--- a/plugins/cube/cube.cpp +++ b/plugins/cube/cube.cpp @@ -1,5 +1,9 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -44,6 +48,8 @@ #include "shaders.tpp" #include "shaders-3-2.tpp" +namespace +{ class wayfire_cube : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t { class fixed_view_overlay_node_t : public wf::scene::node_t @@ -289,7 +295,25 @@ OpenGL::program_t program; - wf_cube_animation_attribs animation; + std::shared_ptr> runtime_duration = + std::make_shared>( + "cube-runtime-animation", wf::animation_description_t{}); + wf_cube_animation_attribs animation{runtime_duration}; + + void start_animation(int duration_limit_ms = -1) + { + // Bound each turn by the repeat interval without changing the user's + // normal animation setting or leaving rotations queued after release. + auto description = animation.animation_duration.value(); + if (duration_limit_ms >= 0) + { + description.length_ms = std::min(description.length_ms, duration_limit_ms); + } + + runtime_duration->set_value(description); + animation.cube_animation.start(); + } + wf::option_wrapper_t use_light{"cube/light"}; wf::option_wrapper_t use_deform{"cube/deform"}; @@ -334,10 +358,20 @@ wf::plugin_activation_data_t grab_interface{ .name = "cube", .capabilities = 0, - .cancel = [=] () { deactivate(); }, + .cancel = [=] () + { + if (cancel_key_repeat) + { + cancel_key_repeat(); + } + + deactivate(); + }, }; public: + std::function cancel_key_repeat; + void init() override { input_grab = std::make_unique("cube", output, nullptr, this, nullptr); @@ -349,7 +383,7 @@ animation.cube_animation.zoom.set(1, 1); animation.cube_animation.ease_deformation.set(0, 0); - animation.cube_animation.start(); + start_animation(); reload_background(); @@ -459,7 +493,7 @@ animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.offset_z.set(offset_z, offset_z); - animation.cube_animation.start(); + start_animation(); update_view_matrix(); output->render->schedule_redraw(); } @@ -502,7 +536,7 @@ animation.cube_animation.rotation.set(0, 0); } - animation.cube_animation.start(); + start_animation(); update_view_matrix(); output->render->schedule_redraw(); } @@ -824,7 +858,7 @@ } /* Start moving to a workspace to the left/right using the keyboard */ - bool move_vp(int dir) + bool move_vp(int dir, int duration_limit_ms = -1) { if (!activate()) { @@ -860,11 +894,11 @@ break; } } - animation.cube_animation.rotation.set( - dir * animation.side_angle, dir * animation.side_angle); - animation.cube_animation.rotation.restart_with_end(0); - - animation.cube_animation.start(); + // Keep the current visual angle when a second step arrives mid-turn. + const float start_angle = animation.cube_animation.rotation + dir * animation.side_angle; + animation.cube_animation.rotation.set(start_angle, 0); + + start_animation(duration_limit_ms); update_view_matrix(); output->render->schedule_redraw(); @@ -874,6 +908,11 @@ /* Initiate with an button grab. */ bool input_grabbed() { + if (cancel_key_repeat) + { + cancel_key_repeat(); + } + if (!activate()) { return false; @@ -905,7 +944,7 @@ animation.cube_animation.zoom.set(current_zoom, current_zoom); animation.cube_animation.ease_deformation.restart_with_end(1); - animation.cube_animation.start(); + start_animation(); update_view_matrix(); output->render->schedule_redraw(); @@ -957,7 +996,7 @@ animation.cube_animation.zoom.set(1.0, 1.0); animation.cube_animation.ease_deformation.set(0, 0); - animation.cube_animation.start(); + start_animation(); update_view_matrix(); output->render->schedule_redraw(); } @@ -1178,7 +1217,7 @@ animation.cube_animation.ease_deformation.restart_with_end( animation.cube_animation.ease_deformation.end); - animation.cube_animation.start(); + start_animation(); output->render->schedule_redraw(); } @@ -1206,7 +1245,7 @@ target_zoom = std::min(std::max(target_zoom, ZOOM_MIN), ZOOM_MAX); animation.cube_animation.zoom.set(start_zoom, target_zoom); - animation.cube_animation.start(); + start_animation(); output->render->schedule_redraw(); } @@ -1300,12 +1339,186 @@ }; +/* Keep key activation data local to Cube; IPC/mouse calls remain single steps. */ +class cube_rotation_binding_t +{ + wf::option_wrapper_t option; + wf::shared_data::ref_ptr_t repo; + std::string name; + public: + using handler_t = std::function; + handler_t handler; + explicit cube_rotation_binding_t(const std::string& name) : option(name), name(name) + { + wf::get_core().bindings->add_activator(option, &binding); + repo->register_method(name, [this] (const wf::json_t& data) + { + auto view_id = wf::ipc::json_get_optional_view_id(data); + if (view_id && !wf::ipc::find_view_by_id(*view_id)) + { + return wf::ipc::json_error("view id not found!"); + } + + auto output = wf::get_core().seat->get_active_output(); + auto id = wf::ipc::json_get_optional_int64(data, "output_id"); + if (!id) + { + id = wf::ipc::json_get_optional_int64(data, "output-id"); + } + if (id) + { + output = wf::ipc::find_output_by_id(*id); + } + if (!output) + { + return wf::ipc::json_error("output id not found!"); + } + if (handler) + { + handler(output, nullptr); + } + return wf::ipc::json_ok(); + }); + } + ~cube_rotation_binding_t() + { + wf::get_core().bindings->rem_binding(&binding); + repo->unregister_method(name); + } + private: + wf::activator_callback binding = [this] (const wf::activator_data_t& data) + { + return handler && handler(wf::get_core().seat->get_active_output(), &data); + }; +}; + class wayfire_cube_global : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t { - wf::ipc_activator_t rotate_left{"cube/rotate_left"}; - wf::ipc_activator_t rotate_right{"cube/rotate_right"}; + cube_rotation_binding_t rotate_left{"cube/rotate_left"}; + cube_rotation_binding_t rotate_right{"cube/rotate_right"}; wf::ipc_activator_t activate{"cube/activate"}; + wf::option_wrapper_t repeat_enabled{"cube/repeat_enabled"}; + wf::option_wrapper_t repeat_acceleration{"cube/repeat_acceleration"}; + wf::option_wrapper_t repeat_delay{"cube/repeat_delay"}; + wf::option_wrapper_t repeat_interval{"cube/repeat_interval"}; + wf::option_wrapper_t repeat_min_interval{"cube/repeat_min_interval"}; + wf::option_wrapper_t repeat_acceleration_time{"cube/repeat_acceleration_time"}; + wf::wl_timer repeat_timer; + wf::output_t *repeat_output = nullptr; + uint32_t repeat_key = 0, repeat_modifiers = 0; + int repeat_direction = 0; + std::chrono::steady_clock::time_point acceleration_started; + + void stop_repeat() + { + repeat_timer.disconnect(); + repeat_output = nullptr; + repeat_key = repeat_modifiers = 0; + } + + int next_interval() const + { + const int slow = std::max(1, int(repeat_interval)); + const int fast = std::clamp(int(repeat_min_interval), 1, slow); + if (!repeat_acceleration) + { + return slow; + } + + double elapsed = std::chrono::duration( + std::chrono::steady_clock::now() - acceleration_started).count(); + double progress = std::clamp(elapsed / std::max(1, int(repeat_acceleration_time)), 0.0, 1.0); + // Smoothstep starts gently and settles at the configured maximum speed. + progress = progress * progress * (3.0 - 2.0 * progress); + return std::max(1, int(std::lround(slow + (fast - slow) * progress))); + } + + void repeat_step() + { + if (!repeat_output || !repeat_enabled || + wf::get_core().seat->get_active_output() != repeat_output || + (wf::get_core().seat->get_keyboard_modifiers() & repeat_modifiers) != repeat_modifiers || + repeat_output->is_inhibited()) + { + stop_repeat(); + return; + } + auto instance = output_instance.find(repeat_output); + int interval = next_interval(); + if (instance == output_instance.end() || + !instance->second->move_vp(repeat_direction, interval)) + { + stop_repeat(); + return; + } + repeat_timer.set_timeout(interval, [this] { repeat_step(); }); + } + + bool rotate(wf::output_t *output, const wf::activator_data_t *data, int direction) + { + stop_repeat(); + auto instance = output_instance.find(output); + if (instance == output_instance.end() || output->is_inhibited() || + !instance->second->move_vp(direction)) + { + return false; + } + + if (data && data->source == wf::activator_source_t::KEYBINDING && + data->activation_data && repeat_enabled) + { + repeat_output = output; + repeat_key = data->activation_data; + repeat_modifiers = wf::get_core().seat->get_keyboard_modifiers(); + repeat_direction = direction; + int delay = std::max(1, int(repeat_delay)); + acceleration_started = std::chrono::steady_clock::now() + std::chrono::milliseconds(delay); + repeat_timer.set_timeout(delay, [this] { repeat_step(); }); + } + return true; + } + + wf::signal::connection_t> on_key = [this] (auto ev) + { + if (!repeat_output) + { + return; + } + + if ((ev->event->keycode == repeat_key && ev->event->state == WLR_KEY_RELEASED) || + (ev->event->keycode != repeat_key && ev->event->state == WLR_KEY_PRESSED) || + (ev->event->state == WLR_KEY_RELEASED && + (wf::get_core().seat->modifier_from_keycode(ev->event->keycode) & repeat_modifiers))) + { + stop_repeat(); + } + }; + wf::signal::connection_t> on_modifiers = + [this] (auto) + { + if (repeat_output && + (wf::get_core().seat->get_keyboard_modifiers() & repeat_modifiers) != repeat_modifiers) + { + stop_repeat(); + } + }; + wf::signal::connection_t on_config = [this] (auto) { stop_repeat(); }; + + void handle_new_output(wf::output_t *output) override + { + wf::per_output_tracker_mixin_t::handle_new_output(output); + output_instance.at(output)->cancel_key_repeat = [this] { stop_repeat(); }; + } + void handle_output_removed(wf::output_t *output) override + { + if (repeat_output == output) + { + stop_repeat(); + } + + wf::per_output_tracker_mixin_t::handle_output_removed(output); + } public: void init() override @@ -1317,32 +1530,28 @@ LOGE("cube: requires GLES2 support, but current renderer is ", render_type); return; } - this->init_output_tracking(); - rotate_left.set_handler(rotate_left_cb); - rotate_right.set_handler(rotate_right_cb); - activate.set_handler(activate_cb); + rotate_left.handler = [this] (auto output, auto data) { return rotate(output, data, -1); }; + rotate_right.handler = [this] (auto output, auto data) { return rotate(output, data, +1); }; + activate.set_handler([this] (wf::output_t *output, wayfire_view) + { + stop_repeat(); + return output_instance.at(output)->input_grabbed(); + }); + wf::get_core().connect(&on_key); + wf::get_core().connect(&on_modifiers); + wf::get_core().connect(&on_config); } void fini() override { + stop_repeat(); + on_key.disconnect(); + on_modifiers.disconnect(); + on_config.disconnect(); this->fini_output_tracking(); } - - wf::ipc_activator_t::handler_t rotate_left_cb = [=] (wf::output_t *output, wayfire_view) - { - return this->output_instance[output]->move_vp(-1); - }; - - wf::ipc_activator_t::handler_t rotate_right_cb = [=] (wf::output_t *output, wayfire_view) - { - return this->output_instance[output]->move_vp(+1); - }; - - wf::ipc_activator_t::handler_t activate_cb = [=] (wf::output_t *output, wayfire_view) - { - return this->output_instance[output]->input_grabbed(); - }; }; +} // Keep hot-reloaded implementation symbols distinct from a resident old cube. DECLARE_WAYFIRE_PLUGIN(wayfire_cube_global); --- a/plugins/cube/cube.hpp +++ b/plugins/cube/cube.hpp @@ -27,6 +27,11 @@ wf::option_wrapper_t animation_duration{"cube/initial_animation"}; cube_animation_t cube_animation{animation_duration}; + explicit wf_cube_animation_attribs( + std::shared_ptr> duration) : + cube_animation(duration) + {} + glm::mat4 projection, view; float side_angle; --- a/metadata/cube.xml +++ b/metadata/cube.xml @@ -22,6 +22,47 @@ <_short>Rotate right <_long>Rotates right with the specified activator. none + + + + <_short>Keyboard repeat + + + + + +