setting: Add scrollbar to SettingPage. (#1660)

This commit is contained in:
Jason Lee 2025-11-21 23:04:56 +08:00 committed by GitHub
parent 857f7973e3
commit fc36451a27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 104 additions and 76 deletions

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
App, AppContext, Axis, Context, Element, Entity, FocusHandle, Focusable, Global, IntoElement, App, AppContext, Axis, Context, Element, Entity, FocusHandle, Focusable, Global, IntoElement,
ParentElement as _, Render, SharedString, Styled, Window, ParentElement as _, Render, SharedString, Styled, Window, px,
}; };
use gpui_component::{ use gpui_component::{
@ -99,6 +99,10 @@ impl super::Story for SettingsStory {
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> { fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {
Self::view(window, cx) Self::view(window, cx)
} }
fn paddings() -> gpui::Pixels {
px(0.)
}
} }
impl SettingsStory { impl SettingsStory {

View file

@ -281,7 +281,7 @@ impl Render for SidebarStory {
Sidebar::new(self.side) Sidebar::new(self.side)
.collapsed(self.collapsed) .collapsed(self.collapsed)
.w(px(220.)) .w(px(220.))
.p_3() .gap_0()
.header( .header(
SidebarHeader::new() SidebarHeader::new()
.child( .child(

View file

@ -1,14 +1,14 @@
use gpui::{ use gpui::{
list, prelude::FluentBuilder as _, px, App, Entity, InteractiveElement as _, IntoElement, div, list, prelude::FluentBuilder as _, px, App, Entity, InteractiveElement as _, IntoElement,
ParentElement as _, SharedString, StatefulInteractiveElement, Styled, Window, ListAlignment, ListState, ParentElement as _, SharedString, Styled, Window,
}; };
use rust_i18n::t; use rust_i18n::t;
use crate::{ use crate::{
button::{Button, ButtonVariants}, button::{Button, ButtonVariants},
divider::Divider,
h_flex, h_flex,
label::Label, label::Label,
scroll::{Scrollbar, ScrollbarState},
setting::{settings::SettingsState, RenderOptions, SettingGroup}, setting::{settings::SettingsState, RenderOptions, SettingGroup},
v_flex, ActiveTheme, IconName, Sizable, v_flex, ActiveTheme, IconName, Sizable,
}; };
@ -100,11 +100,16 @@ impl SettingPage {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let groups_count = groups.len(); let groups_count = groups.len();
let list_state = window let (scroll_state, list_state) = window
.use_keyed_state( .use_keyed_state(
SharedString::from(format!("list-state:{}", ix)), SharedString::from(format!("list-state:{}", ix)),
cx, cx,
|_, _| gpui::ListState::new(groups_count, gpui::ListAlignment::Top, px(0.)), |_, _| {
(
ScrollbarState::default(),
ListState::new(groups_count, ListAlignment::Top, px(100.)),
)
},
) )
.read(cx) .read(cx)
.clone(); .clone();
@ -123,12 +128,13 @@ impl SettingPage {
v_flex() v_flex()
.id(ix) .id(ix)
.p_4()
.size_full() .size_full()
.overflow_scroll()
.child( .child(
v_flex() v_flex()
.p_4()
.gap_3() .gap_3()
.border_b_1()
.border_color(cx.theme().border)
.child(h_flex().justify_between().child(self.title.clone()).when( .child(h_flex().justify_between().child(self.title.clone()).when(
self.is_resettable(cx), self.is_resettable(cx),
|this| { |this| {
@ -153,22 +159,37 @@ impl SettingPage {
.text_sm() .text_sm()
.text_color(cx.theme().muted_foreground), .text_color(cx.theme().muted_foreground),
) )
}) }),
.child(Divider::horizontal()),
) )
.child( .child(
list(list_state.clone(), { div()
let query = query.clone(); .px_4()
let options = *options; .relative()
move |ix, window, cx| { .flex_1()
let group = groups[ix].clone(); .w_full()
group .child(
.pt_6() list(list_state.clone(), {
.render(ix, &query, &options, window, cx) let query = query.clone();
.into_any_element() let options = *options;
} move |ix, window, cx| {
}) let group = groups[ix].clone();
.size_full(), group
.py_4()
.render(ix, &query, &options, window, cx)
.into_any_element()
}
})
.size_full(),
)
.child(
div()
.absolute()
.top_0()
.left_0()
.right_0()
.bottom_0()
.child(Scrollbar::vertical(&scroll_state, &list_state)),
),
) )
} }
} }

View file

@ -4,11 +4,11 @@ use crate::{
resizable::{h_resizable, resizable_panel}, resizable::{h_resizable, resizable_panel},
setting::{SettingGroup, SettingPage}, setting::{SettingGroup, SettingPage},
sidebar::{Sidebar, SidebarMenu, SidebarMenuItem}, sidebar::{Sidebar, SidebarMenu, SidebarMenuItem},
IconName, Sizable, Size, IconName, Sizable, Size, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, relative, App, AppContext as _, Axis, ElementId, Entity, div, prelude::FluentBuilder as _, px, relative, App, AppContext as _, Axis, ElementId, Entity,
IntoElement, ParentElement as _, Pixels, RenderOnce, Styled, Window, IntoElement, ParentElement as _, Pixels, RenderOnce, StyleRefinement, Styled, Window,
}; };
use rust_i18n::t; use rust_i18n::t;
@ -31,6 +31,7 @@ pub struct Settings {
group_variant: GroupBoxVariant, group_variant: GroupBoxVariant,
size: Size, size: Size,
sidebar_width: Pixels, sidebar_width: Pixels,
sidebar_style: StyleRefinement,
} }
impl Settings { impl Settings {
@ -42,6 +43,7 @@ impl Settings {
group_variant: GroupBoxVariant::default(), group_variant: GroupBoxVariant::default(),
size: Size::default(), size: Size::default(),
sidebar_width: px(250.0), sidebar_width: px(250.0),
sidebar_style: StyleRefinement::default(),
} }
} }
@ -71,6 +73,12 @@ impl Settings {
self self
} }
/// Set the style refinement for the sidebar.
pub fn sidebar_style(mut self, style: &StyleRefinement) -> Self {
self.sidebar_style = style.clone();
self
}
fn filtered_pages(&self, query: &str) -> Vec<SettingPage> { fn filtered_pages(&self, query: &str) -> Vec<SettingPage> {
self.pages self.pages
.iter() .iter()
@ -138,6 +146,7 @@ impl Settings {
Sidebar::left() Sidebar::left()
.w(relative(1.)) .w(relative(1.))
.border_0() .border_0()
.refine_style(&self.sidebar_style)
.collapsed(false) .collapsed(false)
.header( .header(
div() div()
@ -145,57 +154,52 @@ impl Settings {
.child(Input::new(&search_input).prefix(IconName::Search)), .child(Input::new(&search_input).prefix(IconName::Search)),
) )
.child( .child(
SidebarMenu::new() SidebarMenu::new().children(pages.iter().enumerate().map(|(page_ix, page)| {
.p_2() let is_page_active =
.children(pages.iter().enumerate().map(|(page_ix, page)| { selected_index.page_ix == page_ix && selected_index.group_ix.is_none();
let is_page_active = SidebarMenuItem::new(page.title.clone())
selected_index.page_ix == page_ix && selected_index.group_ix.is_none(); .default_open(page.default_open)
SidebarMenuItem::new(page.title.clone()) .active(is_page_active)
.default_open(page.default_open) .on_click({
.active(is_page_active) let state = state.clone();
.on_click({ move |_, _, cx| {
let state = state.clone(); state.update(cx, |state, cx| {
move |_, _, cx| { state.selected_index = SelectIndex {
state.update(cx, |state, cx| { page_ix,
state.selected_index = SelectIndex { ..Default::default()
page_ix, };
..Default::default() cx.notify();
}; })
cx.notify(); }
}) })
} .when(page.groups.len() > 1, |this| {
}) this.children(
.when(page.groups.len() > 1, |this| { page.groups
this.children( .iter()
page.groups .filter(|g| g.title.is_some())
.iter() .enumerate()
.filter(|g| g.title.is_some()) .map(|(group_ix, group)| {
.enumerate() let is_active = selected_index.page_ix == page_ix
.map(|(group_ix, group)| { && selected_index.group_ix == Some(group_ix);
let is_active = selected_index.page_ix == page_ix let title = group.title.clone().unwrap_or_default();
&& selected_index.group_ix == Some(group_ix);
let title = group.title.clone().unwrap_or_default();
SidebarMenuItem::new(title).active(is_active).on_click( SidebarMenuItem::new(title).active(is_active).on_click({
{ let state = state.clone();
let state = state.clone(); move |_, _, cx| {
move |_, _, cx| { state.update(cx, |state, cx| {
state.update(cx, |state, cx| { state.selected_index = SelectIndex {
state.selected_index = SelectIndex { page_ix,
page_ix, group_ix: Some(group_ix),
group_ix: Some(group_ix), };
}; state.deferred_scroll_group_ix = Some(group_ix);
state.deferred_scroll_group_ix = cx.notify();
Some(group_ix); })
cx.notify(); }
}) })
} }),
}, )
) })
}), })),
)
})
})),
) )
} }
} }

View file

@ -194,7 +194,6 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
.w(DEFAULT_WIDTH) .w(DEFAULT_WIDTH)
.flex_shrink_0() .flex_shrink_0()
.h_full() .h_full()
.gap_3()
.overflow_hidden() .overflow_hidden()
.relative() .relative()
.bg(cx.theme().sidebar) .bg(cx.theme().sidebar)