From a9d52e49c8d7e87783841fa08eb8cc0644e5e90f Mon Sep 17 00:00:00 2001 From: Benedikt Willi Date: Fri, 9 Jan 2026 14:34:48 +0100 Subject: [PATCH] Improve tab bar visual integration with modern browser-like styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced tab widget design: - Unified tab widget with integrated close button (no visual separation) - Close button on the right with rounded right corners - Tab button with rounded left corners for seamless integration - Custom CSS styling for professional appearance - Hover effects: close button highlights in warning color - Tab highlights as suggested-action when active Visual improvements: - Tabs have rounded corners (4px radius) - Proper borders matching theme colors - Better padding and spacing - Flat buttons by default except active tab - Close button marked with ✕ instead of × - Better visual feedback on hover UX improvements: - Tooltips on all buttons (tab URL, close tab, new tab) - Small square close button (32px) for easy clicking - Active tab uses blue suggested-action styling - Tabs show full URL on hover - New tab uses about:startpage instead of example.com CSS styling: - Theme-aware colors using @view_bg_color, @borders, @warning_color - Proper border-radius on tab and close button - Visual separation between tab and close button - Hover state for better feedback All tests passing (15/15) --- .../__pycache__/chrome.cpython-313.pyc | Bin 14841 -> 16181 bytes src/browser/chrome.py | 110 ++++++++++++++---- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/src/browser/__pycache__/chrome.cpython-313.pyc b/src/browser/__pycache__/chrome.cpython-313.pyc index 1e9e281a30c162c882408318e4278ec6c655a2d4..f23dc621f9fe0fe5d0792283d93d8501016b65c1 100644 GIT binary patch delta 4540 zcmaJ^du&_f6~EW7Tt8wbahy1Q=Rp%Esndj{Bq;^;NtpR~$*Z-AY`M&jEm6awAo-VDJ{9oG{tCPQcru7bMgpcU<=!Xo%5a1r+ zX;njQO%oTg6Z4RTSOOgIHG3*arIQ1M z$;Cy?B@D4nX@Zt{&sLI|l_e60B}isk3bbvB&x%P!42?*`}W^XMW?l$T* z<9xW0dW?Sl8x@<~c2iNYc3evXo?7}^tI3MKtm<1w-#64(nOSVB+%dYP!l4OwU<~z^ z+xoHY%wC71GExB6mDG)K;ZBT+FF>u|47C-Z^<~h|8q`*`1|8QzpqB*)-Bw|z*PH^> zc8SGz(l^%Gsm*B87KQlGDn}FUrcb$jma=Gj3fU8MaFQ-MV)ALicVUox^;uIRKfsn6 zSkgC4wYHiv7c}9`%!P+dKJ#jt!k7f3y%>p~_M3f}|A2s=9~+ zYXCfJEX&DORwA1$_NqwTnpVg&+u-j*muSXgUpwQ zO1|)gdH{7;54vx=TxEAaKd#5)aYmeLw#&TU2Zw2Hz{5-%#tq>E=qCNbXx72Yj<2Q} zcwbBXxE|{CA-7M*Zsta}uW+0BD7fu{qJ=m`T++9VPWqI^MxQs<@-ft`cfnqqMfaY? zLbsXiY#;t>!UX|02u!XCz(shBBB~C~wRku})9WJn+ z{<&JQGec%YQj}Y~sv5#lDkH|{Ej!W*jG*!Ga3~bIiOpWs+?V;hO3qGANwOlv*NZXq z;HD0?ZVE_nq^K22CX<@bh=g|7NMki)O=Y4JVDzF!)kG(V8+bAf&%klwp})5Ed1d6( z0Qt{x?p57yEWfm@^bhCyhx7d-#80cL9=Nj~HiH_%)R{otWWj4Ph{%#ISd+VvYzC60 z&s4d2hVCjy8wiWf^9A2o{dgbhq)$=^k8*8Uqg>q z2lQ;t(xqzef?1Z7XeOe?B!mn6m|vQ;Fi0nk!9u9|j@hIj899U0p)tX7Kgcr+%< z(O629W!11Xt0LXY`*zejEzU(}lW`>xjU_}P7mX#Al!WXlNg+5$RDsz6 zuSo;E(od_KX}-ESpx;TvX-PG*NE69aTs3beB2?ql79xqnN#&XokqUO!s_w`{W==IF zSRSAfh+2^rk3<<$5hvv6B-3SrV3e#};Fs3m8GavpH^3gKmsWe*4li)zN1*VaUjA+1 zs_IWG=N>!r*h+nCuD*5Iy<+Rj+4^o+Y^QcTvFpj9>%sLWwx1e4IlN-=7l&fr*GLC7rZ&adp?mDT0eBU z7flyT%Z{A0>xAyfN|dZTS-B#t%L(h2j^u^V4YzM`@WSBoj-0#a1ZZ}G$co_13CzC7cVRMsK$7dBiM zKy!0WXkOaB($br2>CFp$EJ5cEojtUqv+*d~>Zw>z(o%8w@u*n4N>}HXpuvU#yIy)^95n3%~wVl zNXMF8!Iujt_bia2QS#hZl_Y_oI<~Q2Q=w=k9fb)JgAoez@e+-;3_^StT7IGPRH2+c zE=^>oRGoY{DXJBbESZE>N?de0_?F=X5Fkc+Px~fbp!4nP49saKeX+fVf1SS5?&e>h zpR~8SSOzCho3&^ajK-lqUcS1q;~3`iN@s|#JOdIzeKDi-qxBsPENOby55JU#OSC)W z<=>&Bq5A>+d}u%KVql|chM#`$TQQVSrc>n4=-*#x-G;zHJXdlJUC_u6?}ze*WV*`eOevGXtPl z&_+84+|_u9kjHE~hTk7D(a~K4Eep&~{MI8zBv>$oLu6Jy$JbTMs&MyoCZ0`6_YfX( ziRqNFeSNIudHx-)>2I9zJt*lFdCvC_uIEo& U&%1Wc{Fz}y$9LS~kTP5U2VbvR$p8QV delta 3540 zcmaJ@YitzP6`s4#*_Um+53hG^XBI!#fY)H`VDm_L7zgwA+9ZY2WxPAK*Iw^BcV>CF zrfTDov^1F00Ck!IQhwkjX&Si?p-K%=5~*q%6}PSkQ&3SsmFS-!sPWMZaDh7n8tsqNXgNCsEuzwxR z98eW1#fNBSRFN9GdN)gbo5D?Z@PKV$qKn(dv}7hNr*vbNHd24sxV))3Y@{Z(%~ld4 zx{bk(hWCxT7*?3Z%>j0~FTswsYSIhxGB32& zc|1|lPI`qDYUEF^&?CZICN~V%xl5ylsIg$PBowxY3zk~l9#bpHiAchpys!W_&0hFV zn+tYUxeYy~$a9#ZmUcs$9g7hW%N8Vx-` zrp*Iy6gi0DlvG%JEWc=X-nT*MkuX?QQ88-ccUw?6=V6LHPbvLhlwj16r??-Yz_ zI=c^kV0UwW@Wap_EAW2vbKptL0cW%XU79xb8GF`qOU?5vHRjWZt@RpbM3Cl^;9T0}a2MVr+Yhz4OQ_z;@dLH7|A=erbuu$s??DaX# z-Ur!(zCmBO>~P(tbP^jz@~|RlLyCm`M;;qXCF3%-9qB+aoyZ($g5NJ#(|O0tVwW{C z`16@#(?mC?GE$gzHmPu1=H58i( z^)S%QooS68qS&K!5jAMaK}~nyXh=CYCZ`h=+ly{thbe4Qh7-pIDzn%OuBD1heQ?xw ze6fkUP&e)G&m7dPT4s!Sm+E#^leMghV-@|sPm(0&->1tOim?>ceJq|I9}{l-%Hh4T zgspN`tUQ~T6RV-y9~!Tk6|2rZJ12%d6pOElp;<9>J~SuR@R>9#O6M0aCZFAB-u4I2 z*{AFmy|ezvanrY4*Mn8(I;J`IwN=ztskBy(aT)5&)>oY^|LbxvFe8v;rAL!bf5gTGKi6`yY|Cuz%r_@ozA$ilJulxTw8( zbo%Jk$okpH`YRj0Fc;ZPF(bon9)2pbv2Ih+*-?ecnv%;#6Vt8ogr%$4K3$FQBr;uc zA`y$LYAl|TRaG}@NiC(&PTYSA_w8u3Yrn#+>%nC!)K)(xZeqi*NXubMb&%A-P<6x- zz}=nj3R@VSxm10OSkSIGfysK$OtiL#I53g9L39j$ROb!QF4SVkWv4MqQ@JX~EWBN} zjJyD4^`k5d&($9hd7&CRWwQ7As5lC%8mbAvzJ?k*o~)Z<{aPActoOmI z4X~VRF!5r_+3+CX=8CUqn! z>(*$N4#;tZ`r$iGzqXt}trk!sD~TB{MCx*>Swjw-U6rAjX|kMg^` zkCg8-2$%A!&^Lr?wldEqBut|Y+dw9*#Z>lfqi@31_CvYkJq^1$ByxS`K*vfJ0hn3& zYMT$)_Ghz7R^j0Z6RujtD$hHCD|F_gI3_34nh8#VQ#+{=p^Qy zInMz%+tFutZ{oL|F?b4()^f^0gj zWilz1T5*wwA63y?2wXEJnJ%sx%_OoZWexo+o1){@b_REigpg}O)h)qxLkRv+D7q;a qt_r2U6G~6Eo~@k|q(2EuW`!j;gvwij 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_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 update_address_bar(self): if not self.address_bar: return