Implement 3+ file switcher (#40214)

Release Notes:

- N/A
This commit is contained in:
Mikayla Maki
2025-10-14 23:08:15 +00:00
committed by GitHub
parent 06af052e6d
commit 0d84651f14
2 changed files with 120 additions and 32 deletions
+100 -24
View File
@@ -506,6 +506,7 @@ pub struct SettingsWindow {
title_bar: Option<Entity<PlatformTitleBar>>,
original_window: Option<WindowHandle<Workspace>>,
files: Vec<(SettingsUiFile, FocusHandle)>,
drop_down_file: Option<usize>,
worktree_root_dirs: HashMap<WorktreeId, String>,
current_file: SettingsUiFile,
pages: Vec<SettingsPage>,
@@ -971,6 +972,7 @@ impl SettingsWindow {
original_window,
worktree_root_dirs: HashMap::default(),
files: vec![],
drop_down_file: None,
current_file: current_file,
pages: vec![],
navbar_entries: vec![],
@@ -1440,7 +1442,7 @@ impl SettingsWindow {
.iter()
.any(|(file, _)| file == &self.current_file);
if !current_file_still_exists {
self.change_file(0, window, cx);
self.change_file(0, window, false, cx);
}
}
@@ -1460,12 +1462,22 @@ impl SettingsWindow {
self.open_navbar_entry_page(first_navbar_entry_index);
}
fn change_file(&mut self, ix: usize, window: &mut Window, cx: &mut Context<SettingsWindow>) {
fn change_file(
&mut self,
ix: usize,
window: &mut Window,
drop_down_file: bool,
cx: &mut Context<SettingsWindow>,
) {
if ix >= self.files.len() {
self.current_file = SettingsUiFile::User;
self.build_ui(window, cx);
return;
}
if drop_down_file {
self.drop_down_file = Some(ix);
}
if self.files[ix].0 == self.current_file {
return;
}
@@ -1485,9 +1497,30 @@ impl SettingsWindow {
fn render_files_header(
&self,
_window: &mut Window,
window: &mut Window,
cx: &mut Context<SettingsWindow>,
) -> impl IntoElement {
const OVERFLOW_LIMIT: usize = 1;
let file_button =
|ix, file: &SettingsUiFile, focus_handle, cx: &mut Context<SettingsWindow>| {
Button::new(
ix,
self.display_name(&file)
.expect("Files should always have a name"),
)
.toggle_state(file == &self.current_file)
.selected_style(ButtonStyle::Tinted(ui::TintColor::Accent))
.track_focus(focus_handle)
.on_click(cx.listener({
let focus_handle = focus_handle.clone();
move |this, _: &gpui::ClickEvent, window, cx| {
this.change_file(ix, window, false, cx);
focus_handle.focus(window);
}
}))
};
let this = cx.entity();
h_flex()
.w_full()
.pb_4()
@@ -1499,31 +1532,73 @@ impl SettingsWindow {
.child(
h_flex()
.id("file_buttons_container")
.w_64() // Temporary fix until long-term solution is a fixed set of buttons representing a file location (User, Project, and Remote)
.gap_1()
.overflow_x_scroll()
.children(
self.files
.iter()
.enumerate()
.map(|(ix, (file, focus_handle))| {
Button::new(
ix,
self.display_name(&file)
.expect("Files should always have a name"),
self.files.iter().enumerate().take(OVERFLOW_LIMIT).map(
|(ix, (file, focus_handle))| file_button(ix, file, focus_handle, cx),
),
)
.when(self.files.len() > OVERFLOW_LIMIT, |div| {
div.children(
self.files
.iter()
.enumerate()
.skip(OVERFLOW_LIMIT)
.find(|(_, (file, _))| file == &self.current_file)
.map(|(ix, (file, focus_handle))| {
file_button(ix, file, focus_handle, cx)
})
.or_else(|| {
let ix = self.drop_down_file.unwrap_or(OVERFLOW_LIMIT);
self.files.get(ix).map(|(file, focus_handle)| {
file_button(ix, file, focus_handle, cx)
})
}),
)
.when(
self.files.len() > OVERFLOW_LIMIT + 1,
|div| {
div.child(
DropdownMenu::new(
"more-files",
format!("+{}", self.files.len() - (OVERFLOW_LIMIT + 1)),
ContextMenu::build(window, cx, move |mut menu, _, _| {
for (ix, (file, focus_handle)) in self
.files
.iter()
.enumerate()
.skip(OVERFLOW_LIMIT + 1)
{
menu = menu.entry(
self.display_name(file)
.expect("Files should always have a name"),
None,
{
let this = this.clone();
let focus_handle = focus_handle.clone();
move |window, cx| {
this.update(cx, |this, cx| {
this.change_file(
ix, window, true, cx,
);
});
focus_handle.focus(window);
}
},
);
}
menu
}),
)
.style(DropdownStyle::Ghost)
.tab_index(0)
.no_chevron(),
)
.toggle_state(file == &self.current_file)
.selected_style(ButtonStyle::Tinted(ui::TintColor::Accent))
.track_focus(focus_handle)
.on_click(cx.listener({
let focus_handle = focus_handle.clone();
move |this, _: &gpui::ClickEvent, window, cx| {
this.change_file(ix, window, cx);
focus_handle.focus(window);
}
}))
}),
),
},
)
}),
)
.child(
Button::new("edit-in-json", "Edit in settings.json")
@@ -2675,6 +2750,7 @@ mod test {
worktree_root_dirs: HashMap::default(),
files: Vec::default(),
current_file: crate::SettingsUiFile::User,
drop_down_file: None,
pages,
search_bar: cx.new(|cx| Editor::single_line(window, cx)),
navbar_entry: selected_idx.expect("Must have a selected navbar entry"),
+20 -8
View File
@@ -30,6 +30,7 @@ pub struct DropdownMenu {
attach: Option<Corner>,
offset: Option<Point<Pixels>>,
tab_index: Option<isize>,
chevron: bool,
}
impl DropdownMenu {
@@ -50,6 +51,7 @@ impl DropdownMenu {
attach: None,
offset: None,
tab_index: None,
chevron: true,
}
}
@@ -70,6 +72,7 @@ impl DropdownMenu {
attach: None,
offset: None,
tab_index: None,
chevron: true,
}
}
@@ -109,6 +112,11 @@ impl DropdownMenu {
self.tab_index = Some(arg);
self
}
pub fn no_chevron(mut self) -> Self {
self.chevron = false;
self
}
}
impl Disableable for DropdownMenu {
@@ -132,19 +140,23 @@ impl RenderOnce for DropdownMenu {
let button = match self.label {
LabelKind::Text(text) => Button::new(self.id.clone(), text)
.style(button_style)
.icon(IconName::ChevronUpDown)
.icon_position(IconPosition::End)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.when(self.chevron, |this| {
this.icon(IconName::ChevronUpDown)
.icon_position(IconPosition::End)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
})
.when(full_width, |this| this.full_width())
.size(trigger_size)
.disabled(self.disabled),
LabelKind::Element(_element) => Button::new(self.id.clone(), "")
.style(button_style)
.icon(IconName::ChevronUpDown)
.icon_position(IconPosition::End)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.when(self.chevron, |this| {
this.icon(IconName::ChevronUpDown)
.icon_position(IconPosition::End)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
})
.when(full_width, |this| this.full_width())
.size(trigger_size)
.disabled(self.disabled),