diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 136eb992112b5ffda0762e92e969e96788debd33..ea83187a66d7335e0f1649de6307c13143c55962 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -512,6 +512,7 @@ pub struct SettingsWindow { title_bar: Option>, original_window: Option>, files: Vec<(SettingsUiFile, FocusHandle)>, + drop_down_file: Option, worktree_root_dirs: HashMap, current_file: SettingsUiFile, pages: Vec, @@ -977,6 +978,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![], @@ -1446,7 +1448,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); } } @@ -1466,12 +1468,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) { + fn change_file( + &mut self, + ix: usize, + window: &mut Window, + drop_down_file: bool, + cx: &mut Context, + ) { 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; } @@ -1491,9 +1503,30 @@ impl SettingsWindow { fn render_files_header( &self, - _window: &mut Window, + window: &mut Window, cx: &mut Context, ) -> impl IntoElement { + const OVERFLOW_LIMIT: usize = 1; + + let file_button = + |ix, file: &SettingsUiFile, focus_handle, cx: &mut Context| { + 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() @@ -1505,31 +1538,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") @@ -2692,6 +2767,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"), diff --git a/crates/ui/src/components/dropdown_menu.rs b/crates/ui/src/components/dropdown_menu.rs index 8a1abc312748bdd1fdb087973708d58579ffbc1d..7a4d26db2dedc10d2353578ed99fa83dbe8ed0b5 100644 --- a/crates/ui/src/components/dropdown_menu.rs +++ b/crates/ui/src/components/dropdown_menu.rs @@ -30,6 +30,7 @@ pub struct DropdownMenu { attach: Option, offset: Option>, tab_index: Option, + 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),