From 708c434bd4c21614a282fc31ae35d3e9414ec2c9 Mon Sep 17 00:00:00 2001 From: Daniel Sauble Date: Fri, 15 Aug 2025 04:43:29 -0700 Subject: [PATCH] workspace: Highlight where dragged tab will be dropped (#34740) Closes #18565 I could use some advice on the color palette / theming. A couple options: 1. The `drop_target_background` color could be used for the border if we didn't use it for the background of the tab. In VSCode, the background color of tabs doesn't change as you're dragging, there's just a border between tabs. My only concern with this option is that the current `drop_target_background` color is a bit subtle when used for a small area like a border. 2. Another option could be to add a `drop_target_border` theme color, but I don't know how much complexity this adds to implementation (presumably all existing themes would need to be updated?). Demo: https://github.com/user-attachments/assets/0b7c04ea-5ec5-4b45-adad-156dfbf552db Release Notes: - Highlight where a dragged tab will be dropped between two other tabs --------- Co-authored-by: Smit Barmase --- crates/theme/src/default_colors.rs | 2 ++ crates/theme/src/fallback_themes.rs | 1 + crates/theme/src/schema.rs | 8 ++++++++ crates/theme/src/styles/colors.rs | 4 ++++ crates/workspace/src/pane.rs | 15 +++++++++++++-- 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 1c3f48b548d3fdd4a2a554b476afaa08dcbae150..051b7acf102597b6f11581afdd45611b9a4b76e3 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -54,6 +54,7 @@ impl ThemeColors { element_disabled: neutral().light_alpha().step_3(), element_selection_background: blue().light().step_3().alpha(0.25), drop_target_background: blue().light_alpha().step_2(), + drop_target_border: neutral().light().step_12(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light_alpha().step_3(), ghost_element_active: neutral().light_alpha().step_4(), @@ -179,6 +180,7 @@ impl ThemeColors { element_disabled: neutral().dark_alpha().step_3(), element_selection_background: blue().dark().step_3().alpha(0.25), drop_target_background: blue().dark_alpha().step_2(), + drop_target_border: neutral().dark().step_12(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark_alpha().step_4(), ghost_element_active: neutral().dark_alpha().step_5(), diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index 4d77dd5d81dfc45427bda4034ff7a2085dbcb489..e9e8e2d0db9320a4ec6bf95c53a11c84d1887777 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -115,6 +115,7 @@ pub(crate) fn zed_default_dark() -> Theme { element_disabled: SystemColors::default().transparent, element_selection_background: player.local().selection.alpha(0.25), drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0), + drop_target_border: hsla(221. / 360., 11. / 100., 86. / 100., 1.0), ghost_element_background: SystemColors::default().transparent, ghost_element_hover: hover, ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index bfa2adcedf73ec9d51c25d30785b1e81cd83173e..425fedbc717bb65ce3ce0872e1ed56fef3b79bb9 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -225,6 +225,10 @@ pub struct ThemeColorsContent { #[serde(rename = "drop_target.background")] pub drop_target_background: Option, + /// Border Color. Used for the border that shows where a dragged element will be dropped. + #[serde(rename = "drop_target.border")] + pub drop_target_border: Option, + /// Used for the background of a ghost element that should have the same background as the surface it's on. /// /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... @@ -747,6 +751,10 @@ impl ThemeColorsContent { .drop_target_background .as_ref() .and_then(|color| try_parse_color(color).ok()), + drop_target_border: self + .drop_target_border + .as_ref() + .and_then(|color| try_parse_color(color).ok()), ghost_element_background: self .ghost_element_background .as_ref() diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index aab11803f4d810453f5bfc286624ea8e4efb4a61..198ad97adb5d964a1d8f62c5bde99d1d5be5adf7 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -59,6 +59,8 @@ pub struct ThemeColors { pub element_disabled: Hsla, /// Background Color. Used for the area that shows where a dragged element will be dropped. pub drop_target_background: Hsla, + /// Border Color. Used for the border that shows where a dragged element will be dropped. + pub drop_target_border: Hsla, /// Used for the background of a ghost element that should have the same background as the surface it's on. /// /// Elements might include: Buttons, Inputs, Checkboxes, Radio Buttons... @@ -304,6 +306,7 @@ pub enum ThemeColorField { ElementSelected, ElementDisabled, DropTargetBackground, + DropTargetBorder, GhostElementBackground, GhostElementHover, GhostElementActive, @@ -418,6 +421,7 @@ impl ThemeColors { ThemeColorField::ElementSelected => self.element_selected, ThemeColorField::ElementDisabled => self.element_disabled, ThemeColorField::DropTargetBackground => self.drop_target_background, + ThemeColorField::DropTargetBorder => self.drop_target_border, ThemeColorField::GhostElementBackground => self.ghost_element_background, ThemeColorField::GhostElementHover => self.ghost_element_hover, ThemeColorField::GhostElementActive => self.ghost_element_active, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index cffeea0a8d4452ebff011ca3dd03cd3b10357143..45bd497705c05cf9750e0549fb56116262d78826 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2478,8 +2478,19 @@ impl Pane { }, |tab, _, _, cx| cx.new(|_| tab.clone()), ) - .drag_over::(|tab, _, _, cx| { - tab.bg(cx.theme().colors().drop_target_background) + .drag_over::(move |tab, dragged_tab: &DraggedTab, _, cx| { + let mut styled_tab = tab + .bg(cx.theme().colors().drop_target_background) + .border_color(cx.theme().colors().drop_target_border) + .border_0(); + + if ix < dragged_tab.ix { + styled_tab = styled_tab.border_l_2(); + } else if ix > dragged_tab.ix { + styled_tab = styled_tab.border_r_2(); + } + + styled_tab }) .drag_over::(|tab, _, _, cx| { tab.bg(cx.theme().colors().drop_target_background)