Skip to main content

gpui_component/setting/
group.rs

1use std::rc::Rc;
2
3use gpui::{
4    AnyElement, App, IntoElement, ParentElement as _, SharedString, StyleRefinement, Styled,
5    Window, prelude::FluentBuilder as _,
6};
7
8use crate::{
9    ActiveTheme, StyledExt,
10    group_box::{GroupBox, GroupBoxVariants},
11    label::Label,
12    setting::{RenderOptions, SettingItem},
13    v_flex,
14};
15
16/// A setting group that can contain multiple setting items.
17#[derive(Clone)]
18pub struct SettingGroup {
19    style: StyleRefinement,
20    footer: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
21
22    pub(super) title: Option<SharedString>,
23    pub(super) description: Option<SharedString>,
24    pub(super) items: Vec<SettingItem>,
25}
26
27impl Styled for SettingGroup {
28    fn style(&mut self) -> &mut StyleRefinement {
29        &mut self.style
30    }
31}
32
33impl SettingGroup {
34    /// Create a new setting group.
35    pub fn new() -> Self {
36        Self {
37            style: StyleRefinement::default(),
38            footer: None,
39            title: None,
40            description: None,
41            items: Vec::new(),
42        }
43    }
44
45    /// Set the label of the setting group, default is None.
46    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
47        self.title = Some(title.into());
48        self
49    }
50
51    /// Set the description of the setting group, default is None.
52    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
53        self.description = Some(description.into());
54        self
55    }
56
57    /// Render supporting content below, and outside, the group's surface.
58    ///
59    /// The footer aligns with the group title and renders as small muted text,
60    /// like a description. It scrolls with the group and follows its search
61    /// visibility; it does not add an independently searchable item or a
62    /// sidebar entry, and a group needs at least one item to be shown.
63    pub fn footer<F, E>(mut self, footer: F) -> Self
64    where
65        E: IntoElement,
66        F: Fn(&mut Window, &mut App) -> E + 'static,
67    {
68        self.footer = Some(Rc::new(move |window, cx| {
69            footer(window, cx).into_any_element()
70        }));
71        self
72    }
73
74    /// Add a setting item to the group.
75    pub fn item(mut self, item: SettingItem) -> Self {
76        self.items.push(item);
77        self
78    }
79
80    /// Add multiple setting items to the group.
81    pub fn items<I>(mut self, items: I) -> Self
82    where
83        I: IntoIterator<Item = SettingItem>,
84    {
85        self.items.extend(items);
86        self
87    }
88
89    /// Return true if any of the setting items in the group match the given query.
90    pub(super) fn is_match(&self, query: &str, cx: &App) -> bool {
91        self.items.iter().any(|item| item.is_match(query, cx))
92    }
93
94    pub(super) fn is_resettable(&self, query: &str, cx: &App) -> bool {
95        self.items
96            .iter()
97            .any(|item| item.is_match(query, cx) && item.is_resettable(cx))
98    }
99
100    pub(crate) fn render(
101        self,
102        query: &str,
103        options: &RenderOptions,
104        window: &mut Window,
105        cx: &mut App,
106    ) -> impl IntoElement {
107        GroupBox::new()
108            .id(SharedString::from(format!("group-{}", options.group_ix())))
109            .with_variant(options.group_variant())
110            .when_some(self.title.clone(), |this, title| {
111                this.title(v_flex().gap_1().child(title).when_some(
112                    self.description.clone(),
113                    |this, description| {
114                        this.child(
115                            Label::new(description)
116                                .text_sm()
117                                .text_color(cx.theme().muted_foreground),
118                        )
119                    },
120                ))
121            })
122            .gap_4()
123            .children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {
124                if item.is_match(&query, cx) {
125                    Some(
126                        item.clone()
127                            .render_item(&options.with_item_ix(item_ix), window, cx),
128                    )
129                } else {
130                    None
131                }
132            }))
133            .when_some(self.footer, |this, footer| this.footer(footer(window, cx)))
134            .refine_style(&self.style)
135    }
136
137    pub(crate) fn reset(&self, query: &str, window: &mut Window, cx: &mut App) {
138        for item in &self.items {
139            if item.is_match(query, cx) {
140                item.reset(window, cx);
141            }
142        }
143    }
144}