1use crate::{prelude::*, Checkbox, ContextMenu, ListHeader};
2
3use super::DropdownMenu;
4
5#[derive(PartialEq, Clone, Eq, Debug)]
6pub enum ToggleType {
7 Checkbox,
8 // Switch,
9}
10
11impl From<ToggleType> for SettingType {
12 fn from(toggle_type: ToggleType) -> Self {
13 SettingType::Toggle(toggle_type)
14 }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum InputType {
19 Text,
20 Number,
21}
22
23impl From<InputType> for SettingType {
24 fn from(input_type: InputType) -> Self {
25 SettingType::Input(input_type)
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum SecondarySettingType {
31 Dropdown,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum SettingType {
36 Toggle(ToggleType),
37 ToggleAnd(SecondarySettingType),
38 Input(InputType),
39 Dropdown,
40 Range,
41 Unsupported,
42}
43
44#[derive(Debug, Clone, IntoElement)]
45pub struct LegacySettingsGroup {
46 pub name: String,
47 settings: Vec<SettingsItem>,
48}
49
50impl LegacySettingsGroup {
51 pub fn new(name: impl Into<String>) -> Self {
52 Self {
53 name: name.into(),
54 settings: Vec::new(),
55 }
56 }
57
58 pub fn add_setting(mut self, setting: SettingsItem) -> Self {
59 self.settings.push(setting);
60 self
61 }
62}
63
64impl RenderOnce for LegacySettingsGroup {
65 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
66 let empty_message = format!("No settings available for {}", self.name);
67
68 let header = ListHeader::new(self.name);
69
70 let settings = self.settings.clone().into_iter();
71
72 v_flex()
73 .p_1()
74 .gap_2()
75 .child(header)
76 .when(self.settings.len() == 0, |this| {
77 this.child(Label::new(empty_message))
78 })
79 .children(settings)
80 }
81}
82
83#[derive(Debug, Clone, PartialEq)]
84pub enum SettingLayout {
85 Stacked,
86 AutoWidth,
87 FullLine,
88 FullLineJustified,
89}
90
91#[derive(Debug, Clone, PartialEq)]
92pub struct SettingId(pub SharedString);
93
94impl From<SettingId> for ElementId {
95 fn from(id: SettingId) -> Self {
96 ElementId::Name(id.0)
97 }
98}
99
100impl From<&str> for SettingId {
101 fn from(id: &str) -> Self {
102 Self(id.to_string().into())
103 }
104}
105
106impl From<SharedString> for SettingId {
107 fn from(id: SharedString) -> Self {
108 Self(id)
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
113pub struct SettingValue(pub SharedString);
114
115impl From<SharedString> for SettingValue {
116 fn from(value: SharedString) -> Self {
117 Self(value)
118 }
119}
120
121impl From<String> for SettingValue {
122 fn from(value: String) -> Self {
123 Self(value.into())
124 }
125}
126
127impl From<bool> for SettingValue {
128 fn from(value: bool) -> Self {
129 Self(value.to_string().into())
130 }
131}
132
133impl From<SettingValue> for bool {
134 fn from(value: SettingValue) -> Self {
135 value.0 == "true"
136 }
137}
138
139#[derive(Debug, Clone, IntoElement)]
140pub struct SettingsItem {
141 pub id: SettingId,
142 current_value: Option<SettingValue>,
143 disabled: bool,
144 hide_label: bool,
145 icon: Option<IconName>,
146 layout: SettingLayout,
147 name: SharedString,
148 // possible_values: Option<Vec<SettingValue>>,
149 setting_type: SettingType,
150 toggled: Option<bool>,
151}
152
153impl SettingsItem {
154 pub fn new(
155 id: impl Into<SettingId>,
156 name: SharedString,
157 setting_type: SettingType,
158 current_value: Option<SettingValue>,
159 ) -> Self {
160 let toggled = match setting_type {
161 SettingType::Toggle(_) | SettingType::ToggleAnd(_) => Some(false),
162 _ => None,
163 };
164
165 Self {
166 id: id.into(),
167 current_value,
168 disabled: false,
169 hide_label: false,
170 icon: None,
171 layout: SettingLayout::FullLine,
172 name,
173 // possible_values: None,
174 setting_type,
175 toggled,
176 }
177 }
178
179 pub fn layout(mut self, layout: SettingLayout) -> Self {
180 self.layout = layout;
181 self
182 }
183
184 pub fn toggled(mut self, toggled: bool) -> Self {
185 self.toggled = Some(toggled);
186 self
187 }
188
189 // pub fn hide_label(mut self, hide_label: bool) -> Self {
190 // self.hide_label = hide_label;
191 // self
192 // }
193
194 pub fn icon(mut self, icon: IconName) -> Self {
195 self.icon = Some(icon);
196 self
197 }
198
199 // pub fn disabled(mut self, disabled: bool) -> Self {
200 // self.disabled = disabled;
201 // self
202 // }
203}
204
205impl RenderOnce for SettingsItem {
206 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
207 let id: ElementId = self.id.clone().into();
208
209 // When the setting is disabled or toggled off, we don't want any secondary elements to be interactable
210 let _secondary_element_disabled = self.disabled || self.toggled == Some(false);
211
212 let full_width = match self.layout {
213 SettingLayout::FullLine | SettingLayout::FullLineJustified => true,
214 _ => false,
215 };
216
217 let hide_label = self.hide_label || self.icon.is_some();
218
219 let justified = match (self.layout.clone(), self.setting_type.clone()) {
220 (_, SettingType::ToggleAnd(_)) => true,
221 (SettingLayout::FullLineJustified, _) => true,
222 _ => false,
223 };
224
225 let (setting_type, current_value) = (self.setting_type.clone(), self.current_value.clone());
226 let current_string = if let Some(current_value) = current_value.clone() {
227 Some(current_value.0)
228 } else {
229 None
230 };
231
232 let toggleable = match setting_type {
233 SettingType::Toggle(_) => true,
234 SettingType::ToggleAnd(_) => true,
235 _ => false,
236 };
237
238 let setting_element = match setting_type {
239 SettingType::Toggle(_) => None,
240 SettingType::ToggleAnd(secondary_setting_type) => match secondary_setting_type {
241 SecondarySettingType::Dropdown => Some(
242 DropdownMenu::new(
243 id.clone(),
244 current_string.unwrap_or_default(),
245 ContextMenu::build(cx, |menu, _cx| menu),
246 )
247 .into_any_element(),
248 ),
249 },
250 SettingType::Input(input_type) => match input_type {
251 InputType::Text => Some(div().child("text").into_any_element()),
252 InputType::Number => Some(div().child("number").into_any_element()),
253 },
254 SettingType::Dropdown => Some(
255 DropdownMenu::new(
256 id.clone(),
257 current_string.unwrap_or_default(),
258 ContextMenu::build(cx, |menu, _cx| menu),
259 )
260 .full_width(true)
261 .into_any_element(),
262 ),
263 SettingType::Range => Some(div().child("range").into_any_element()),
264 SettingType::Unsupported => None,
265 };
266
267 let checkbox = Checkbox::new(
268 ElementId::Name(format!("toggle-{}", self.id.0).to_string().into()),
269 self.toggled.into(),
270 )
271 .disabled(self.disabled);
272
273 let toggle_element = match (toggleable, self.setting_type.clone()) {
274 (true, SettingType::Toggle(toggle_type)) => match toggle_type {
275 ToggleType::Checkbox => Some(checkbox.into_any_element()),
276 },
277 (true, SettingType::ToggleAnd(_)) => Some(checkbox.into_any_element()),
278 (_, _) => None,
279 };
280
281 let item = if self.layout == SettingLayout::Stacked {
282 v_flex()
283 } else {
284 h_flex()
285 };
286
287 item.id(id)
288 .gap_2()
289 .w_full()
290 .when_some(self.icon, |this, icon| {
291 this.child(div().px_0p5().child(Icon::new(icon).color(Color::Muted)))
292 })
293 .children(toggle_element)
294 .children(if hide_label {
295 None
296 } else {
297 Some(Label::new(self.name.clone()))
298 })
299 .when(justified, |this| this.child(div().flex_1().size_full()))
300 .child(
301 h_flex()
302 .when(full_width, |this| this.w_full())
303 .when(self.layout == SettingLayout::FullLineJustified, |this| {
304 this.justify_end()
305 })
306 .children(setting_element),
307 )
308 // help flex along when full width is disabled
309 //
310 // this probably isn't needed, but fighting with flex to
311 // get this right without inspection tools will be a pain
312 .when(!full_width, |this| this.child(div().size_full().flex_1()))
313 }
314}
315
316pub struct LegacySettingsMenu {
317 name: SharedString,
318 groups: Vec<LegacySettingsGroup>,
319}
320
321impl LegacySettingsMenu {
322 pub fn new(name: impl Into<SharedString>) -> Self {
323 Self {
324 name: name.into(),
325 groups: Vec::new(),
326 }
327 }
328
329 pub fn add_group(mut self, group: LegacySettingsGroup) -> Self {
330 self.groups.push(group);
331 self
332 }
333}
334
335impl Render for LegacySettingsMenu {
336 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
337 let is_empty = self.groups.is_empty();
338 v_flex()
339 .id(ElementId::Name(self.name.clone()))
340 .elevation_2(cx)
341 .min_w_56()
342 .max_w_96()
343 .max_h_2_3()
344 .px_2()
345 .map(|el| {
346 if is_empty {
347 el.py_1()
348 } else {
349 el.pt_0().pb_1()
350 }
351 })
352 .gap_1()
353 .when(is_empty, |this| {
354 this.child(Label::new("No settings found").color(Color::Muted))
355 })
356 .children(self.groups.clone())
357 }
358}