From d8f15d83d36d8e51cf6ed897f2513ec58e371204 Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Fri, 17 Jul 2026 18:24:20 -0700 Subject: [PATCH] fix(ui): polish settings tab scrolling after #538 (#541) * fix(ui): polish settings tab scrolling after oversized-dialog fix Forward wheel events from unfocused combos/spins to the tab scroller, drop the nested Advanced ScrolledWindow, and clear tab scroller shadows. * refactor(ui): re-dispatch scroll to parent ScrolledWindow Drop hand-rolled vadjustment math; forward the event and let GTK scroll. --- src/vocalinux/ui/settings_dialog.py | 37 +++++++++++------------------ 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/vocalinux/ui/settings_dialog.py b/src/vocalinux/ui/settings_dialog.py index 14dcc34..36acbbb 100644 --- a/src/vocalinux/ui/settings_dialog.py +++ b/src/vocalinux/ui/settings_dialog.py @@ -574,25 +574,20 @@ def _prevent_scroll_on_hover(widget: Gtk.Widget): """ Prevent scroll events from modifying widget values when hovering. - This fixes a common GTK UX issue where scrolling through a settings dialog - accidentally changes ComboBox or SpinButton values when the mouse happens - to be over them. The widget will only respond to scroll events after being - explicitly clicked/focused. - - Args: - widget: The widget to prevent scroll events on (ComboBox, SpinButton, etc.) + Unfocused ComboBox/SpinButton widgets ignore wheel input so scrolling a + settings tab does not accidentally change values. The event is applied to + the nearest ancestor ScrolledWindow instead, so the page still scrolls. """ def on_scroll(widget, event): - # Only process scroll if widget has explicit focus - if not widget.has_focus(): - # Stop propagation to prevent value changes - return True - return False + if widget.has_focus(): + return False + scrolled = widget.get_ancestor(Gtk.ScrolledWindow) + if scrolled is not None: + scrolled.event(event) + return True widget.connect("scroll-event", on_scroll) - - # Also prevent focus-on-hover behavior widget.set_can_focus(True) @@ -1100,6 +1095,7 @@ class SettingsDialog(Gtk.Dialog): def _scrollable(tab): scroller = Gtk.ScrolledWindow() scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroller.set_shadow_type(Gtk.ShadowType.NONE) scroller.add(tab) return scroller @@ -2107,15 +2103,11 @@ class SettingsDialog(Gtk.Dialog): opt_in_group.add_row(power_user_row) self.advanced_tab.pack_start(opt_in_group, False, False, 0) - # Revealer that hides/shows the actual advanced controls + # Revealer that hides/shows the actual advanced controls. + # No inner ScrolledWindow: the notebook tab already scrolls. self.advanced_revealer = Gtk.Revealer() self.advanced_revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN) - # Scrollable container for the controls - scrolled = Gtk.ScrolledWindow() - scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scrolled.set_min_content_height(350) - controls_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) group = PreferencesGroup(title="Whisper.cpp Decoding") @@ -2256,9 +2248,8 @@ class SettingsDialog(Gtk.Dialog): separator.set_margin_bottom(8) controls_box.pack_start(separator, False, False, 0) - scrolled.add(controls_box) - self.advanced_revealer.add(scrolled) - self.advanced_tab.pack_start(self.advanced_revealer, True, True, 0) + self.advanced_revealer.add(controls_box) + self.advanced_tab.pack_start(self.advanced_revealer, False, False, 0) self.advanced_no_timestamps_switch.connect("state-set", self._on_advanced_param_changed) self.advanced_no_context_switch.connect("state-set", self._on_advanced_param_changed)