setting.rs

  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}