gpui_component/setting/
group.rs1use 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, GroupBoxVariant, GroupBoxVariants},
11 label::Label,
12 setting::{RenderOptions, SettingItem},
13 v_flex,
14};
15
16#[derive(Clone)]
18pub struct SettingGroup {
19 style: StyleRefinement,
20 footer: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
21 variant: Option<GroupBoxVariant>,
22
23 pub(super) title: Option<SharedString>,
24 pub(super) description: Option<SharedString>,
25 pub(super) items: Vec<SettingItem>,
26}
27
28impl Styled for SettingGroup {
29 fn style(&mut self) -> &mut StyleRefinement {
30 &mut self.style
31 }
32}
33
34impl SettingGroup {
35 pub fn new() -> Self {
37 Self {
38 style: StyleRefinement::default(),
39 footer: None,
40 variant: None,
41 title: None,
42 description: None,
43 items: Vec::new(),
44 }
45 }
46
47 pub fn title(mut self, title: impl Into<SharedString>) -> Self {
49 self.title = Some(title.into());
50 self
51 }
52
53 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
55 self.description = Some(description.into());
56 self
57 }
58
59 pub fn variant(mut self, variant: GroupBoxVariant) -> Self {
67 self.variant = Some(variant);
68 self
69 }
70
71 pub fn footer<F, E>(mut self, footer: F) -> Self
78 where
79 E: IntoElement,
80 F: Fn(&mut Window, &mut App) -> E + 'static,
81 {
82 self.footer = Some(Rc::new(move |window, cx| {
83 footer(window, cx).into_any_element()
84 }));
85 self
86 }
87
88 pub fn item(mut self, item: SettingItem) -> Self {
90 self.items.push(item);
91 self
92 }
93
94 pub fn items<I>(mut self, items: I) -> Self
96 where
97 I: IntoIterator<Item = SettingItem>,
98 {
99 self.items.extend(items);
100 self
101 }
102
103 pub(super) fn is_match(&self, query: &str, cx: &App) -> bool {
105 self.items.iter().any(|item| item.is_match(query, cx))
106 }
107
108 pub(super) fn is_resettable(&self, query: &str, cx: &App) -> bool {
109 self.items
110 .iter()
111 .any(|item| item.is_match(query, cx) && item.is_resettable(cx))
112 }
113
114 pub(crate) fn render(
115 self,
116 query: &str,
117 options: &RenderOptions,
118 window: &mut Window,
119 cx: &mut App,
120 ) -> impl IntoElement {
121 GroupBox::new()
122 .id(SharedString::from(format!("group-{}", options.group_ix())))
123 .with_variant(self.variant.unwrap_or(options.group_variant()))
124 .when_some(self.title.clone(), |this, title| {
125 this.title(v_flex().gap_1().child(title).when_some(
126 self.description.clone(),
127 |this, description| {
128 this.child(
129 Label::new(description)
130 .text_sm()
131 .text_color(cx.theme().muted_foreground),
132 )
133 },
134 ))
135 })
136 .gap_4()
137 .children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {
138 if item.is_match(&query, cx) {
139 Some(
140 item.clone()
141 .render_item(&options.with_item_ix(item_ix), window, cx),
142 )
143 } else {
144 None
145 }
146 }))
147 .when_some(self.footer, |this, footer| this.footer(footer(window, cx)))
148 .refine_style(&self.style)
149 }
150
151 pub(crate) fn reset(&self, query: &str, window: &mut Window, cx: &mut App) {
152 for item in &self.items {
153 if item.is_match(query, cx) {
154 item.reset(window, cx);
155 }
156 }
157 }
158}