settings_ui: Implement sub pages (#39484)
Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
@@ -6,7 +6,7 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
|
|||||||
use fuzzy::StringMatchCandidate;
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render,
|
App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render,
|
||||||
ScrollHandle, Stateful, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle,
|
ScrollHandle, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle,
|
||||||
WindowOptions, actions, div, point, px, size, uniform_list,
|
WindowOptions, actions, div, point, px, size, uniform_list,
|
||||||
};
|
};
|
||||||
use project::WorktreeId;
|
use project::WorktreeId;
|
||||||
@@ -1358,6 +1358,39 @@ fn user_settings_data() -> Vec<SettingsPage> {
|
|||||||
// }),
|
// }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
SettingsPage {
|
||||||
|
title: "Languages & Frameworks",
|
||||||
|
expanded: false,
|
||||||
|
items: vec![
|
||||||
|
SettingsPageItem::SectionHeader("General"),
|
||||||
|
SettingsPageItem::SettingItem(SettingItem {
|
||||||
|
title: "Enable Language Server",
|
||||||
|
description: "Whether to use language servers to provide code intelligence",
|
||||||
|
field: Box::new(SettingField {
|
||||||
|
pick: |settings_content| {
|
||||||
|
&settings_content
|
||||||
|
.project
|
||||||
|
.all_languages
|
||||||
|
.defaults
|
||||||
|
.enable_language_server
|
||||||
|
},
|
||||||
|
pick_mut: |settings_content| {
|
||||||
|
&mut settings_content
|
||||||
|
.project
|
||||||
|
.all_languages
|
||||||
|
.defaults
|
||||||
|
.enable_language_server
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
metadata: None,
|
||||||
|
}),
|
||||||
|
SettingsPageItem::SectionHeader("Languages"),
|
||||||
|
SettingsPageItem::SubPageLink(SubPageLink {
|
||||||
|
title: "JSON",
|
||||||
|
render: Rc::new(|_, _, _| "A settings page!".into_any_element()),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
SettingsPage {
|
SettingsPage {
|
||||||
title: "Workbench & Window",
|
title: "Workbench & Window",
|
||||||
expanded: false,
|
expanded: false,
|
||||||
@@ -2742,6 +2775,15 @@ pub struct SettingsWindow {
|
|||||||
navbar_entries: Vec<NavBarEntry>,
|
navbar_entries: Vec<NavBarEntry>,
|
||||||
list_handle: UniformListScrollHandle,
|
list_handle: UniformListScrollHandle,
|
||||||
search_matches: Vec<Vec<bool>>,
|
search_matches: Vec<Vec<bool>>,
|
||||||
|
/// The current sub page path that is selected.
|
||||||
|
/// If this is empty the selected page is rendered,
|
||||||
|
/// otherwise the last sub page gets rendered.
|
||||||
|
sub_page_stack: Vec<SubPage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SubPage {
|
||||||
|
link: SubPageLink,
|
||||||
|
section_header: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
@@ -2761,6 +2803,7 @@ struct SettingsPage {
|
|||||||
enum SettingsPageItem {
|
enum SettingsPageItem {
|
||||||
SectionHeader(&'static str),
|
SectionHeader(&'static str),
|
||||||
SettingItem(SettingItem),
|
SettingItem(SettingItem),
|
||||||
|
SubPageLink(SubPageLink),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for SettingsPageItem {
|
impl std::fmt::Debug for SettingsPageItem {
|
||||||
@@ -2770,6 +2813,9 @@ impl std::fmt::Debug for SettingsPageItem {
|
|||||||
SettingsPageItem::SettingItem(setting_item) => {
|
SettingsPageItem::SettingItem(setting_item) => {
|
||||||
write!(f, "SettingItem({})", setting_item.title)
|
write!(f, "SettingItem({})", setting_item.title)
|
||||||
}
|
}
|
||||||
|
SettingsPageItem::SubPageLink(sub_page_link) => {
|
||||||
|
write!(f, "SubPageLink({})", sub_page_link.title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2778,9 +2824,10 @@ impl SettingsPageItem {
|
|||||||
fn render(
|
fn render(
|
||||||
&self,
|
&self,
|
||||||
file: SettingsUiFile,
|
file: SettingsUiFile,
|
||||||
|
section_header: &'static str,
|
||||||
is_last: bool,
|
is_last: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut Context<SettingsWindow>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
match self {
|
match self {
|
||||||
SettingsPageItem::SectionHeader(header) => v_flex()
|
SettingsPageItem::SectionHeader(header) => v_flex()
|
||||||
@@ -2850,6 +2897,36 @@ impl SettingsPageItem {
|
|||||||
))
|
))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
SettingsPageItem::SubPageLink(sub_page_link) => h_flex()
|
||||||
|
.id(sub_page_link.title)
|
||||||
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.flex_wrap()
|
||||||
|
.justify_between()
|
||||||
|
.when(!is_last, |this| {
|
||||||
|
this.pb_4()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
v_flex().max_w_1_2().flex_shrink().child(
|
||||||
|
Label::new(SharedString::new_static(sub_page_link.title))
|
||||||
|
.size(LabelSize::Default),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new(("sub-page".into(), sub_page_link.title), "Configure")
|
||||||
|
.icon(Some(IconName::ChevronRight))
|
||||||
|
.icon_position(Some(IconPosition::End))
|
||||||
|
.style(ButtonStyle::Outlined),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let sub_page_link = sub_page_link.clone();
|
||||||
|
cx.listener(move |this, _, _, cx| {
|
||||||
|
this.push_sub_page(sub_page_link.clone(), section_header, cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.into_any_element(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2873,6 +2950,18 @@ impl PartialEq for SettingItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SubPageLink {
|
||||||
|
title: &'static str,
|
||||||
|
render: Rc<dyn Fn(&mut SettingsWindow, &mut Window, &mut App) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SubPageLink {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.title == other.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
enum SettingsUiFile {
|
enum SettingsUiFile {
|
||||||
@@ -2953,6 +3042,7 @@ impl SettingsWindow {
|
|||||||
search_bar,
|
search_bar,
|
||||||
search_task: None,
|
search_task: None,
|
||||||
search_matches: vec![],
|
search_matches: vec![],
|
||||||
|
sub_page_stack: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.fetch_files(cx);
|
this.fetch_files(cx);
|
||||||
@@ -3063,6 +3153,9 @@ impl SettingsWindow {
|
|||||||
candidates.push(StringMatchCandidate::new(key_index, header));
|
candidates.push(StringMatchCandidate::new(key_index, header));
|
||||||
header_index = item_index;
|
header_index = item_index;
|
||||||
}
|
}
|
||||||
|
SettingsPageItem::SubPageLink(sub_page_link) => {
|
||||||
|
candidates.push(StringMatchCandidate::new(key_index, sub_page_link.title));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
key_lut.push(ItemKey {
|
key_lut.push(ItemKey {
|
||||||
page_index,
|
page_index,
|
||||||
@@ -3239,23 +3332,81 @@ impl SettingsWindow {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_page(&self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Stateful<Div> {
|
fn render_sub_page_breadcrumbs(&self) -> impl IntoElement {
|
||||||
let items: Vec<_> = self.page_items().collect();
|
let mut items = vec![];
|
||||||
let items_len = items.len();
|
items.push(self.current_page().title);
|
||||||
|
items.extend(
|
||||||
|
self.sub_page_stack
|
||||||
|
.iter()
|
||||||
|
.flat_map(|page| [page.section_header, page.link.title]),
|
||||||
|
);
|
||||||
|
|
||||||
v_flex()
|
let last = items.pop().unwrap();
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.children(
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|item| [item, "/"])
|
||||||
|
.map(|item| Label::new(item).color(Color::Muted)),
|
||||||
|
)
|
||||||
|
.child(Label::new(last))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_page(&mut self, window: &mut Window, cx: &mut Context<SettingsWindow>) -> Div {
|
||||||
|
let mut page = v_flex()
|
||||||
|
.w_full()
|
||||||
|
.pt_4()
|
||||||
|
.px_6()
|
||||||
|
.gap_4()
|
||||||
|
.bg(cx.theme().colors().editor_background);
|
||||||
|
let mut page_content = v_flex()
|
||||||
.id("settings-ui-page")
|
.id("settings-ui-page")
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.children(items.into_iter().enumerate().map(|(index, item)| {
|
|
||||||
let is_last = index == items_len - 1;
|
|
||||||
item.render(self.current_file.clone(), is_last, window, cx)
|
|
||||||
}))
|
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.track_scroll(
|
.track_scroll(
|
||||||
window
|
window
|
||||||
.use_state(cx, |_, _| ScrollHandle::default())
|
.use_state(cx, |_, _| ScrollHandle::default())
|
||||||
.read(cx),
|
.read(cx),
|
||||||
)
|
);
|
||||||
|
if self.sub_page_stack.len() == 0 {
|
||||||
|
page = page.child(self.render_files(window, cx));
|
||||||
|
|
||||||
|
let items: Vec<_> = self.page_items().collect();
|
||||||
|
let items_len = items.len();
|
||||||
|
let mut section_header = None;
|
||||||
|
|
||||||
|
page_content =
|
||||||
|
page_content.children(items.into_iter().enumerate().map(|(index, item)| {
|
||||||
|
let is_last = index == items_len - 1;
|
||||||
|
if let SettingsPageItem::SectionHeader(header) = item {
|
||||||
|
section_header = Some(*header);
|
||||||
|
}
|
||||||
|
item.render(
|
||||||
|
self.current_file.clone(),
|
||||||
|
section_header.expect("All items rendered after a section header"),
|
||||||
|
is_last,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
page = page.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(IconButton::new("back-btn", IconName::ChevronLeft).on_click(
|
||||||
|
cx.listener(|this, _, _, cx| {
|
||||||
|
this.pop_sub_page(cx);
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.child(self.render_sub_page_breadcrumbs()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let active_page_render_fn = self.sub_page_stack.last().unwrap().link.render.clone();
|
||||||
|
page_content = page_content.child((active_page_render_fn)(self, window, cx));
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.child(page_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_page_index(&self) -> usize {
|
fn current_page_index(&self) -> usize {
|
||||||
@@ -3282,6 +3433,24 @@ impl SettingsWindow {
|
|||||||
fn is_navbar_entry_selected(&self, ix: usize) -> bool {
|
fn is_navbar_entry_selected(&self, ix: usize) -> bool {
|
||||||
ix == self.navbar_entry
|
ix == self.navbar_entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_sub_page(
|
||||||
|
&mut self,
|
||||||
|
sub_page_link: SubPageLink,
|
||||||
|
section_header: &'static str,
|
||||||
|
cx: &mut Context<SettingsWindow>,
|
||||||
|
) {
|
||||||
|
self.sub_page_stack.push(SubPage {
|
||||||
|
link: sub_page_link,
|
||||||
|
section_header,
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_sub_page(&mut self, cx: &mut Context<SettingsWindow>) {
|
||||||
|
self.sub_page_stack.pop();
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for SettingsWindow {
|
impl Render for SettingsWindow {
|
||||||
@@ -3296,16 +3465,7 @@ impl Render for SettingsWindow {
|
|||||||
.bg(cx.theme().colors().background)
|
.bg(cx.theme().colors().background)
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.child(self.render_nav(window, cx))
|
.child(self.render_nav(window, cx))
|
||||||
.child(
|
.child(self.render_page(window, cx))
|
||||||
v_flex()
|
|
||||||
.w_full()
|
|
||||||
.pt_4()
|
|
||||||
.px_6()
|
|
||||||
.gap_4()
|
|
||||||
.bg(cx.theme().colors().editor_background)
|
|
||||||
.child(self.render_files(window, cx))
|
|
||||||
.child(self.render_page(window, cx)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3637,6 +3797,7 @@ mod test {
|
|||||||
list_handle: UniformListScrollHandle::default(),
|
list_handle: UniformListScrollHandle::default(),
|
||||||
search_matches,
|
search_matches,
|
||||||
search_task: None,
|
search_task: None,
|
||||||
|
sub_page_stack: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
settings_window.build_navbar();
|
settings_window.build_navbar();
|
||||||
|
|||||||
Reference in New Issue
Block a user