--- a/plugins/cube/cube.cpp +++ b/plugins/cube/cube.cpp @@ -1628,29 +1628,31 @@ void pointer_scrolled(double amount) { - if (animation.in_exit) - { - return; - } - - animation.cube_animation.offset_y.restart_with_end( - animation.cube_animation.offset_y.end); - animation.cube_animation.offset_z.restart_with_end( - animation.cube_animation.offset_z.end); - animation.cube_animation.rotation.restart_with_end( - animation.cube_animation.rotation.end); - animation.cube_animation.ease_deformation.restart_with_end( - animation.cube_animation.ease_deformation.end); - - float target_zoom = animation.cube_animation.zoom; - float start_zoom = target_zoom; - - target_zoom += - std::min(std::pow(target_zoom, 1.5f), ZOOM_MAX) * amount * ZVelocity; - target_zoom = std::min(std::max(target_zoom, ZOOM_MIN), ZOOM_MAX); - animation.cube_animation.zoom.set(start_zoom, target_zoom); - - start_animation(); + if (animation.in_exit || !std::isfinite(amount) || (amount == 0)) + { + return; + } + + // Wheel input changes scale directly, just as dragging changes pose. + // Restarting the shared clock also replays the overview fit, and + // retargeting an interpolated zoom loses closely spaced wheel deltas. + // Equal input changes scale by an equal ratio at every zoom level; + // opposite input restores the scale unless a bound was reached. + const double current_zoom = animation.cube_animation.zoom; + const double log_zoom = wf::clamp(std::log(current_zoom) + amount * ZVelocity, + std::log(double(ZOOM_MIN)), std::log(double(ZOOM_MAX))); + const double target_zoom = std::exp(log_zoom); + if (target_zoom == current_zoom) + { + return; + } + + animation.cube_animation.zoom.set(target_zoom, target_zoom); + update_view_matrix(); + if (render_node) + { + wf::scene::damage_node(render_node, render_node->get_bounding_box()); + } output->render->schedule_redraw(); }