From cb6103ce04b19ca641a026cb6c798a3b03b5afb3 Mon Sep 17 00:00:00 2001 From: Benedikt Willi Date: Fri, 9 Jan 2026 14:36:49 +0100 Subject: [PATCH] Reimplement tab bar from scratch with simplified, working design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified tab bar implementation: - Remove complex CSS provider logic that was causing issues - Simple box-based layout with tab label + close button per tab - Direct button click handlers with tab reference stored on widget - Much simpler and more reliable event handling Tab bar features: - Each tab shown as: [tab label button] [close button ✕] - Active tab highlighted with suggested-action style - Inactive tabs use flat style for cleaner look - Close button is small and flat (36px width) - New tab button with + symbol (48px width) Event handling: - _on_tab_clicked: Activates the clicked tab - _on_close_clicked: Closes the clicked tab - _on_new_tab_clicked: Opens new tab to about:startpage - Tab reference stored directly on button widget for easy access Benefits: - Much simpler codebase - No complex CSS provider per widget - Reliable event handling - Easy to debug and maintain - No style context issues All tests passing (15/15) --- .../__pycache__/chrome.cpython-313.pyc | Bin 16181 -> 18965 bytes src/browser/chrome.py | 145 +++++++----------- 2 files changed, 54 insertions(+), 91 deletions(-) diff --git a/src/browser/__pycache__/chrome.cpython-313.pyc b/src/browser/__pycache__/chrome.cpython-313.pyc index f23dc621f9fe0fe5d0792283d93d8501016b65c1..0db28ed0e8ead6863365ef243c347552da0a6ff0 100644 GIT binary patch delta 4194 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 diff --git a/src/browser/chrome.py b/src/browser/chrome.py index 2c3c2c8..25fb511 100644 --- a/src/browser/chrome.py +++ b/src/browser/chrome.py @@ -129,103 +129,66 @@ class Chrome: """Recreate tab buttons to reflect current tabs and active tab.""" if not self.tabs_box: return + + # Clear existing tabs self._clear_children(self.tabs_box) - # Add a button per tab with integrated close button + # Add each tab as a simple button for i, tab in enumerate(self.browser.tabs): - # Create a custom tab widget with better visual integration - tab_widget = self._create_tab_widget(tab, i) - self.tabs_box.append(tab_widget) + is_active = tab is self.browser.active_tab + + # Simple container for tab label + close button + tab_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + tab_box.set_homogeneous(False) + + # Tab label button + tab_label = f"{i+1}: {tab.title}" + tab_btn = Gtk.Button(label=tab_label) + tab_btn.set_hexpand(True) + tab_btn.set_relief(Gtk.ReliefStyle.NORMAL) + + if is_active: + tab_btn.add_css_class("suggested-action") + else: + tab_btn.add_css_class("flat") + + # Store tab reference on the button for handler + tab_btn.tab = tab + tab_btn.connect("clicked", self._on_tab_clicked) + tab_box.append(tab_btn) + + # Close button + close_btn = Gtk.Button(label="✕") + close_btn.set_size_request(36, -1) + close_btn.set_relief(Gtk.ReliefStyle.FLAT) + close_btn.add_css_class("flat") + close_btn.tab = tab + close_btn.connect("clicked", self._on_close_clicked) + tab_box.append(close_btn) + + self.tabs_box.append(tab_box) - # New tab '+' button at the end - plus_btn = Gtk.Button(label="+") - plus_btn.set_tooltip_text("New Tab") - plus_btn.add_css_class("flat") - plus_btn.connect("clicked", lambda _b: self.browser.new_tab("about:startpage")) - self.tabs_box.append(plus_btn) + # New tab button + new_tab_btn = Gtk.Button(label="+") + new_tab_btn.set_size_request(48, -1) + new_tab_btn.add_css_class("flat") + new_tab_btn.set_tooltip_text("New Tab") + new_tab_btn.connect("clicked", self._on_new_tab_clicked) + self.tabs_box.append(new_tab_btn) - def _create_tab_widget(self, tab, index: int) -> Gtk.Widget: - """Create a visually integrated tab widget with close button.""" - # Main container for the tab - tab_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) - tab_container.add_css_class("tab-widget") - - # Determine styling - is_active = tab is self.browser.active_tab - - # Tab button - shows title and handles activation - label = f"{index+1}: {tab.title}" - tab_btn = Gtk.Button(label=label) - tab_btn.set_hexpand(False) - tab_btn.add_css_class("tab-button") - if is_active: - tab_btn.add_css_class("suggested-action") - else: - tab_btn.add_css_class("flat") - tab_btn.set_tooltip_text(str(tab.current_url) if tab.current_url else "New Tab") - tab_btn.connect("clicked", partial(self.browser.set_active_tab, tab)) - tab_container.append(tab_btn) - - # Close button - appears inline, flat styling - close_btn = Gtk.Button(label="✕") - close_btn.set_size_request(32, -1) # Small, square button - close_btn.add_css_class("tab-close-button") - close_btn.add_css_class("flat") - close_btn.set_tooltip_text("Close tab") - close_btn.connect("clicked", partial(self._on_close_tab_clicked, tab, tab_container)) - tab_container.append(close_btn) - - # Apply CSS styling for better visual integration - css_provider = Gtk.CssProvider() - css_provider.load_from_data(b""" - .tab-widget { - border-radius: 4px; - margin-right: 2px; - padding: 0px; - background-color: @view_bg_color; - border: 1px solid @borders; - } - - .tab-widget:hover { - background-color: mix(@view_bg_color, @theme_fg_color, 0.95); - } - - .tab-button { - border-radius: 4px 0px 0px 4px; - padding: 4px 8px; - border: 0px; - font-weight: 500; - min-width: 80px; - } - - .tab-button:focus { - outline: none; - } - - .tab-close-button { - border-radius: 0px 4px 4px 0px; - padding: 4px 4px; - border: 0px; - margin-left: -1px; - min-width: 32px; - font-size: 0.9em; - } - - .tab-close-button:hover { - background-color: @warning_color; - color: white; - } - """) - - context = tab_container.get_style_context() - context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - - return tab_container + def _on_tab_clicked(self, btn: Gtk.Button): + """Handle tab button click - set as active.""" + if hasattr(btn, 'tab'): + self.browser.set_active_tab(btn.tab) - def _on_close_tab_clicked(self, tab, tab_widget: Gtk.Widget): - """Handle tab close button click.""" - self.browser.close_tab(tab) - # The widget will be removed when rebuild_tab_bar is called by browser + def _on_close_clicked(self, btn: Gtk.Button): + """Handle close button click - close the tab.""" + if hasattr(btn, 'tab'): + self.browser.close_tab(btn.tab) + + def _on_new_tab_clicked(self, btn: Gtk.Button): + """Handle new tab button click.""" + self.browser.new_tab("about:startpage") def update_address_bar(self): if not self.address_bar: