setting.rs

  1use crate::{prelude::*, Checkbox, 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 SettingsGroup {
 46    pub name: String,
 47    settings: Vec<SettingsItem>,
 48}
 49
 50impl SettingsGroup {
 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 SettingsGroup {
 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(id.clone(), &cx)
243                        .current_item(current_string)
244                        .disabled(secondary_element_disabled)
245                        .into_any_element(),
246                ),
247            },
248            SettingType::Input(input_type) => match input_type {
249                InputType::Text => Some(div().child("text").into_any_element()),
250                InputType::Number => Some(div().child("number").into_any_element()),
251            },
252            SettingType::Dropdown => Some(
253                DropdownMenu::new(id.clone(), &cx)
254                    .current_item(current_string)
255                    .full_width(true)
256                    .into_any_element(),
257            ),
258            SettingType::Range => Some(div().child("range").into_any_element()),
259            SettingType::Unsupported => None,
260        };
261
262        let checkbox = Checkbox::new(
263            ElementId::Name(format!("toggle-{}", self.id.0).to_string().into()),
264            self.toggled.into(),
265        )
266        .disabled(self.disabled);
267
268        let toggle_element = match (toggleable, self.setting_type.clone()) {
269            (true, SettingType::Toggle(toggle_type)) => match toggle_type {
270                ToggleType::Checkbox => Some(checkbox.into_any_element()),
271            },
272            (true, SettingType::ToggleAnd(_)) => Some(checkbox.into_any_element()),
273            (_, _) => None,
274        };
275
276        let item = if self.layout == SettingLayout::Stacked {
277            v_flex()
278        } else {
279            h_flex()
280        };
281
282        item.id(id)
283            .gap_2()
284            .w_full()
285            .when_some(self.icon, |this, icon| {
286                this.child(div().px_0p5().child(Icon::new(icon).color(Color::Muted)))
287            })
288            .children(toggle_element)
289            .children(if hide_label {
290                None
291            } else {
292                Some(Label::new(self.name.clone()))
293            })
294            .when(justified, |this| this.child(div().flex_1().size_full()))
295            .child(
296                h_flex()
297                    .when(full_width, |this| this.w_full())
298                    .when(self.layout == SettingLayout::FullLineJustified, |this| {
299                        this.justify_end()
300                    })
301                    .children(setting_element),
302            )
303            // help flex along when full width is disabled
304            //
305            // this probably isn't needed, but fighting with flex to
306            // get this right without inspection tools will be a pain
307            .when(!full_width, |this| this.child(div().size_full().flex_1()))
308    }
309}
310
311pub struct SettingsMenu {
312    name: SharedString,
313    groups: Vec<SettingsGroup>,
314}
315
316impl SettingsMenu {
317    pub fn new(name: impl Into<SharedString>) -> Self {
318        Self {
319            name: name.into(),
320            groups: Vec::new(),
321        }
322    }
323
324    pub fn add_group(mut self, group: SettingsGroup) -> Self {
325        self.groups.push(group);
326        self
327    }
328}
329
330impl Render for SettingsMenu {
331    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
332        let is_empty = self.groups.is_empty();
333        v_flex()
334            .id(ElementId::Name(self.name.clone()))
335            .elevation_2(cx)
336            .min_w_56()
337            .max_w_96()
338            .max_h_2_3()
339            .px_2()
340            .when_else(
341                is_empty,
342                |empty| empty.py_1(),
343                |not_empty| not_empty.pt_0().pb_1(),
344            )
345            .gap_1()
346            .when(is_empty, |this| {
347                this.child(Label::new("No settings found").color(Color::Muted))
348            })
349            .children(self.groups.clone())
350    }
351}