gpui_component/setting/
page.rs

1use gpui::{
2    App, Entity, InteractiveElement as _, IntoElement, ListAlignment, ListState,
3    ParentElement as _, SharedString, Styled, Window, div, list, prelude::FluentBuilder as _, px,
4};
5use rust_i18n::t;
6
7use crate::{
8    ActiveTheme, IconName, Sizable,
9    button::{Button, ButtonVariants},
10    h_flex,
11    label::Label,
12    scroll::ScrollableElement,
13    setting::{RenderOptions, SettingGroup, settings::SettingsState},
14    v_flex,
15};
16
17/// A setting page that can contain multiple setting groups.
18#[derive(Clone)]
19pub struct SettingPage {
20    resettable: bool,
21    pub(super) default_open: bool,
22    pub(super) title: SharedString,
23    pub(super) description: Option<SharedString>,
24    pub(super) groups: Vec<SettingGroup>,
25}
26
27impl SettingPage {
28    pub fn new(title: impl Into<SharedString>) -> Self {
29        Self {
30            resettable: true,
31            default_open: false,
32            title: title.into(),
33            description: None,
34            groups: Vec::new(),
35        }
36    }
37
38    /// Set the title of the setting page.
39    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
40        self.title = title.into();
41        self
42    }
43
44    /// Set the description of the setting page, default is None.
45    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
46        self.description = Some(description.into());
47        self
48    }
49
50    /// Set the default open state of the setting page, default is false.
51    pub fn default_open(mut self, default_open: bool) -> Self {
52        self.default_open = default_open;
53        self
54    }
55
56    /// Set whether the setting page is resettable, default is true.
57    ///
58    /// If true and the items in this page has changed, the reset button will appear.
59    pub fn resettable(mut self, resettable: bool) -> Self {
60        self.resettable = resettable;
61        self
62    }
63
64    /// Add a setting group to the page.
65    pub fn group(mut self, group: SettingGroup) -> Self {
66        self.groups.push(group);
67        self
68    }
69
70    /// Add multiple setting groups to the page.
71    pub fn groups(mut self, groups: impl IntoIterator<Item = SettingGroup>) -> Self {
72        self.groups.extend(groups);
73        self
74    }
75
76    fn is_resettable(&self, cx: &App) -> bool {
77        self.resettable && self.groups.iter().any(|group| group.is_resettable(cx))
78    }
79
80    fn reset_all(&self, window: &mut Window, cx: &mut App) {
81        for group in &self.groups {
82            group.reset(window, cx);
83        }
84    }
85
86    pub(super) fn render(
87        &self,
88        ix: usize,
89        state: &Entity<SettingsState>,
90        options: &RenderOptions,
91        window: &mut Window,
92        cx: &mut App,
93    ) -> impl IntoElement {
94        let search_input = state.read(cx).search_input.clone();
95        let query = search_input.read(cx).value();
96        let groups = self
97            .groups
98            .iter()
99            .filter(|group| group.is_match(&query))
100            .cloned()
101            .collect::<Vec<_>>();
102        let groups_count = groups.len();
103
104        let list_state = window
105            .use_keyed_state(
106                SharedString::from(format!("list-state:{}", ix)),
107                cx,
108                |_, _| ListState::new(groups_count, ListAlignment::Top, px(100.)),
109            )
110            .read(cx)
111            .clone();
112
113        if list_state.item_count() != groups_count {
114            list_state.reset(groups_count);
115        }
116
117        let deferred_scroll_group_ix = state.read(cx).deferred_scroll_group_ix;
118        if let Some(ix) = deferred_scroll_group_ix {
119            state.update(cx, |state, _| {
120                state.deferred_scroll_group_ix = None;
121            });
122            list_state.scroll_to_reveal_item(ix);
123        }
124
125        v_flex()
126            .id(ix)
127            .size_full()
128            .child(
129                v_flex()
130                    .p_4()
131                    .gap_3()
132                    .border_b_1()
133                    .border_color(cx.theme().border)
134                    .child(h_flex().justify_between().child(self.title.clone()).when(
135                        self.is_resettable(cx),
136                        |this| {
137                            this.child(
138                                Button::new("reset")
139                                    .icon(IconName::Undo2)
140                                    .ghost()
141                                    .small()
142                                    .tooltip(t!("Settings.Reset All"))
143                                    .on_click({
144                                        let page = self.clone();
145                                        move |_, window, cx| {
146                                            page.reset_all(window, cx);
147                                        }
148                                    }),
149                            )
150                        },
151                    ))
152                    .when_some(self.description.clone(), |this, description| {
153                        this.child(
154                            Label::new(description)
155                                .text_sm()
156                                .text_color(cx.theme().muted_foreground),
157                        )
158                    }),
159            )
160            .child(
161                div()
162                    .px_4()
163                    .relative()
164                    .flex_1()
165                    .w_full()
166                    .child(
167                        list(list_state.clone(), {
168                            let query = query.clone();
169                            let options = *options;
170                            move |group_ix, window, cx| {
171                                let group = groups[group_ix].clone();
172                                group
173                                    .py_4()
174                                    .render(
175                                        &query,
176                                        &RenderOptions {
177                                            page_ix: ix,
178                                            group_ix,
179                                            ..options
180                                        },
181                                        window,
182                                        cx,
183                                    )
184                                    .into_any_element()
185                            }
186                        })
187                        .size_full(),
188                    )
189                    .vertical_scrollbar(&list_state),
190            )
191    }
192}