Let the mouse wheel scroll past spin buttons that do not have the focus

A GtkSpinButton grabs the focus and spins its value on any wheel turn over
it, so scrolling through the params stopped at the first spin under the
pointer and changed it. A capture-phase scroll controller now scrolls the
enclosing view instead, unless the spin already has the focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-23 21:47:10 +02:00
co-authored by Claude Opus 5.5
parent 5292c101db
commit d0b5669f46
+36
View File
@@ -43,6 +43,40 @@ Gtk::Label* sectionLabel(const char* text) {
return label;
}
// A GtkSpinButton takes every wheel turn over it: it grabs the focus and
// spins its value, so scrolling down a column of params stops dead at the
// first spin under the pointer and quietly changes it. This hands the wheel to
// the view instead unless the spin already has the focus -- the user clicked
// into it and means to adjust it. It runs in the capture phase, ahead of the
// spin's own controller, and so has to scroll the enclosing ScrolledWindow
// itself: stopping the event there stops it for the window as well.
void scrollPastUnlessFocused(Gtk::SpinButton& spin) {
auto scroll = Gtk::EventControllerScroll::create();
scroll->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
scroll->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
auto* controller = scroll.get();
scroll->signal_scroll().connect(
[&spin, controller](double, double dy) {
if ((spin.get_state_flags() & Gtk::StateFlags::FOCUS_WITHIN) == Gtk::StateFlags::FOCUS_WITHIN) {
return false;
}
auto* scroller = spin.get_ancestor(GTK_TYPE_SCROLLED_WINDOW);
if (scroller == nullptr) return true;
auto adjustment = static_cast<Gtk::ScrolledWindow*>(scroller)->get_vadjustment();
// The step a ScrolledWindow itself takes for one notch of a wheel;
// a touchpad already reports its deltas in pixels.
double step = dy;
if (controller->get_unit() == Gdk::ScrollUnit::WHEEL) {
step *= std::pow(adjustment->get_page_size(), 2.0 / 3.0);
}
adjustment->set_value(std::clamp(adjustment->get_value() + step, adjustment->get_lower(),
adjustment->get_upper() - adjustment->get_page_size()));
return true;
},
false);
spin.add_controller(scroll);
}
} // namespace
EffectorWindow::EffectorWindow(const Options& initial) {
@@ -281,6 +315,7 @@ Gtk::Widget& EffectorWindow::buildCanvasSection() {
dpiSpin_.set_value(kDefaultDpi);
for (auto* spin : {&widthSpin_, &heightSpin_, &dpiSpin_}) {
spin->set_hexpand(true);
scrollPastUnlessFocused(*spin);
}
for (auto* spin : {&widthSpin_, &heightSpin_}) {
spin->signal_value_changed().connect([this] {
@@ -477,6 +512,7 @@ void EffectorWindow::buildParamWidgets() {
: paramDouble(values, spec.key, spec.defaultValue));
spin->signal_value_changed().connect(
sigc::mem_fun(*this, &EffectorWindow::onParamChanged));
scrollPastUnlessFocused(*spin);
widget = std::move(spin);
break;
}