From 3e55a5dbc0626b1566186d75f235feac26c18cf9 Mon Sep 17 00:00:00 2001 From: Val Alexander <68980965+BunsDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:31:27 -0500 Subject: [PATCH 1/2] =?UTF-8?q?feat(tabs):=20world-class=20active-tab=20st?= =?UTF-8?q?ate=20=E2=80=94=20accent=20underline=20+=20hover=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elevate tab-state legibility in the live new-tab-styling path: - Active tab now carries a full-width 2px accent underline pinned to its bottom edge, visually connecting it to the content area below (the editor/terminal convention used by VS Code, Zed, Windows Terminal). It is a pure `ParentBySize` overlay, so it adds no height and never reflows the strip — tab height stays 34px and drag/drop layout is unaffected. - Inactive tabs now brighten their border on hover (fg_overlay_1 -> fg_overlay_2) to match the existing background lift, giving clear cursor feedback instead of a background-only change. Both reuse existing theme accessors (theme.accent(), internal_colors); no hardcoded colors and no new design primitives. Legacy (flag-off) path is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/src/tab.rs | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/app/src/tab.rs b/app/src/tab.rs index 7fdeb9b8..2708a8eb 100644 --- a/app/src/tab.rs +++ b/app/src/tab.rs @@ -1414,7 +1414,9 @@ impl<'a> TabComponent<'a> { Fill::None }; - let border = if is_active { + // Brighten the border on hover (matching the active treatment) so an + // inactive tab gives clear feedback under the cursor. + let border = if is_active || is_hovered { internal_colors::fg_overlay_2(theme) } else { internal_colors::fg_overlay_1(theme) @@ -1625,9 +1627,35 @@ impl<'a> TabComponent<'a> { .with_border(Border::all(1.).with_border_fill(border_fill)); } + // Mark the active tab with a full-width accent underline pinned to its + // bottom edge, visually connecting it to the content area below. The bar + // is a pure overlay (`ParentBySize` stretches it to the tab width) so it + // adds no height and never reflows the strip. + let tab_element: Box = if FeatureFlag::NewTabStyling.is_enabled() && is_active + { + let accent_underline = + ConstrainedBox::new(Rect::new().with_background(theme.accent()).finish()) + .with_height(2.) + .finish(); + + let mut accented = Stack::new().with_child(tab.finish()); + accented.add_positioned_overlay_child( + accent_underline, + OffsetPositioning::offset_from_parent( + vec2f(0., 0.), + ParentOffsetBounds::ParentBySize, + ParentAnchor::BottomLeft, + ChildAnchor::BottomLeft, + ), + ); + accented.finish() + } else { + tab.finish() + }; + // If the tab is being dragged, add an opaque background behind it if is_tab_dragging { - Container::new(tab.finish()) + Container::new(tab_element) .with_background_color( self.ui_builder .warp_theme() @@ -1641,10 +1669,10 @@ impl<'a> TabComponent<'a> { // semantically meaningful for an element that follows the cursor, // and because the inner `DropTarget`'s position is unrelated to // any real tab in the target window. - tab.finish() + tab_element } else { DropTarget::new( - tab.finish(), + tab_element, TabBarDropTargetData { tab_bar_location: TabBarLocation::TabIndex(self.tab_index), }, From dba03ef32b3406373f69120b483f9dbbb9757bf6 Mon Sep 17 00:00:00 2001 From: Val Alexander <68980965+BunsDev@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:40:29 -0500 Subject: [PATCH 2/2] feat(tabs): redesign tab layout into floating browser-style cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reshape the new-tab-styling strip from abutting square separators into a calmer, modern floating-card layout: - Rounded-top cards (6px) with a flat bottom that merges into the content area below — the browser/editor tab silhouette. - Replace the per-tab separator borders with a small 2px gap between tabs; idle tabs are now fully transparent and borderless for an uncluttered strip. - Active/hovered cards get a subtle top+side outline (open bottom); the active tab additionally keeps its accent underline along the flat edge, making the selected tab unmistakable. - Roomier inner horizontal padding (8 -> 10px) for better text rhythm. All colors come from existing theme accessors (theme.accent(), internal_colors::fg_overlay_*), which are translucent and stack correctly over the strip background. Legacy (flag-off) path is untouched. Verified: cargo check -p warp-app --bin cast-codes --features gui (clean). Co-Authored-By: Claude Opus 4.8 (1M context) --- app/src/tab.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/app/src/tab.rs b/app/src/tab.rs index 2708a8eb..855764a3 100644 --- a/app/src/tab.rs +++ b/app/src/tab.rs @@ -1472,7 +1472,11 @@ impl<'a> TabComponent<'a> { .finish(), ); Container::new(flex_row.finish()) - .with_horizontal_padding(8.) + .with_horizontal_padding(if FeatureFlag::NewTabStyling.is_enabled() { + 10. + } else { + 8. + }) .finish() }; @@ -1614,13 +1618,21 @@ impl<'a> TabComponent<'a> { .with_vertical_padding(2.) .with_background(background_color); if FeatureFlag::NewTabStyling.is_enabled() { - let is_first_tab = self.tab_index == 0; - tab = tab.with_border( - Border::all(1.) - // We only include a left border on the very first tab to avoid double borders. - .with_sides(false, is_first_tab, false, true) - .with_border_fill(border_fill), - ); + // Floating browser-style card: rounded top corners with a flat bottom + // that merges into the content area below. The active tab's accent + // underline (added below) sits along that flat edge. Tabs are spaced + // by a small gap (added in `build`) rather than abutting separators. + tab = tab.with_corner_radius(CornerRadius::with_top(Radius::Pixels(6.0))); + // Only the active/hovered card draws a subtle outline (top + sides, + // open bottom); idle tabs stay borderless for a calm, uncluttered + // strip. + if is_active || is_hovered { + tab = tab.with_border( + Border::all(1.) + .with_sides(true, true, false, true) + .with_border_fill(border_fill), + ); + } } else { tab = tab .with_corner_radius(CornerRadius::with_all(Radius::Pixels(4.0))) @@ -1886,6 +1898,16 @@ impl UiComponent for TabComponent<'_> { .finish() }; + // Separate the floating cards with a small gap instead of abutting + // separators (new styling only). The first tab keeps a tight left edge. + let constrained_tab = if FeatureFlag::NewTabStyling.is_enabled() && tab_index > 0 { + Container::new(constrained_tab) + .with_margin_left(2.) + .finish() + } else { + constrained_tab + }; + // Skip the `Draggable` and `SavePosition` wrappers when rendering // the tab inside the cross-window drag chip overlay. Wrapping the // chip in another `Draggable` would interfere with the in-flight