708 lines · 24.8 KB · Rust
RawDownload
use gpui::{div, hsla, prelude::*, ElementId, Hsla, IntoElement, Styled, WindowContext};use std::sync::Arc; use crate::utils::is_light;use crate::{prelude::*, ElevationIndex};use crate::{Color, Icon, IconName, ToggleState}; // TODO: Checkbox, CheckboxWithLabel, Switch, SwitchWithLabel all could be// restructured to use a ToggleLike, similar to Button/Buttonlike, Label/Labellike /// Creates a new checkbox.pub fn checkbox(id: impl Into<ElementId>, toggle_state: ToggleState) -> Checkbox {    Checkbox::new(id, toggle_state)} /// Creates a new switch.pub fn switch(id: impl Into<ElementId>, toggle_state: ToggleState) -> Switch {    Switch::new(id, toggle_state)} /// The visual style of a toggle.#[derive(Debug, Default, Clone, PartialEq, Eq)]pub enum ToggleStyle {    /// Toggle has a transparent background    #[default]    Ghost,    /// Toggle has a filled background based on the    /// elevation index of the parent container    ElevationBased(ElevationIndex),    /// A custom style using a color to tint the toggle    Custom(Hsla),} /// # Checkbox////// Checkboxes are used for multiple choices, not for mutually exclusive choices./// Each checkbox works independently from other checkboxes in the list,/// therefore checking an additional box does not affect any other selections.#[derive(IntoElement)]pub struct Checkbox {    id: ElementId,    toggle_state: ToggleState,    disabled: bool,    on_click: Option<Box<dyn Fn(&ToggleState, &mut WindowContext) + 'static>>,    filled: bool,    style: ToggleStyle,} impl Checkbox {    /// Creates a new [`Checkbox`].    pub fn new(id: impl Into<ElementId>, checked: ToggleState) -> Self {        Self {            id: id.into(),            toggle_state: checked,            disabled: false,            on_click: None,            filled: false,            style: ToggleStyle::default(),        }    }     /// Sets the disabled state of the [`Checkbox`].    pub fn disabled(mut self, disabled: bool) -> Self {        self.disabled = disabled;        self    }     /// Binds a handler to the [`Checkbox`] that will be called when clicked.    pub fn on_click(        mut self,        handler: impl Fn(&ToggleState, &mut WindowContext) + 'static,    ) -> Self {        self.on_click = Some(Box::new(handler));        self    }     /// Sets the `fill` setting of the checkbox, indicating whether it should be filled.    pub fn fill(mut self) -> Self {        self.filled = true;        self    }     /// Sets the style of the checkbox using the specified [`ToggleStyle`].    pub fn style(mut self, style: ToggleStyle) -> Self {        self.style = style;        self    }     /// Match the style of the checkbox to the current elevation using [`ToggleStyle::ElevationBased`].    pub fn elevation(mut self, elevation: ElevationIndex) -> Self {        self.style = ToggleStyle::ElevationBased(elevation);        self    }} impl Checkbox {    fn bg_color(&self, cx: &WindowContext) -> Hsla {        let style = self.style.clone();        match (style, self.filled) {            (ToggleStyle::Ghost, false) => cx.theme().colors().ghost_element_background,            (ToggleStyle::Ghost, true) => cx.theme().colors().element_background,            (ToggleStyle::ElevationBased(_), false) => gpui::transparent_black(),            (ToggleStyle::ElevationBased(elevation), true) => elevation.darker_bg(cx),            (ToggleStyle::Custom(_), false) => gpui::transparent_black(),            (ToggleStyle::Custom(color), true) => color.opacity(0.2),        }    }     fn border_color(&self, cx: &WindowContext) -> Hsla {        if self.disabled {            return cx.theme().colors().border_disabled;        }         match self.style.clone() {            ToggleStyle::Ghost => cx.theme().colors().border_variant,            ToggleStyle::ElevationBased(elevation) => elevation.on_elevation_bg(cx),            ToggleStyle::Custom(color) => color.opacity(0.3),        }    }} impl RenderOnce for Checkbox {    fn render(self, cx: &mut WindowContext) -> impl IntoElement {        let group_id = format!("checkbox_group_{:?}", self.id);        let icon = match self.toggle_state {            ToggleState::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(                if self.disabled {                    Color::Disabled                } else {                    Color::Selected                },            )),            ToggleState::Indeterminate => Some(                Icon::new(IconName::Dash)                    .size(IconSize::Small)                    .color(if self.disabled {                        Color::Disabled                    } else {                        Color::Selected                    }),            ),            ToggleState::Unselected => None,        };         let bg_color = self.bg_color(cx);        let border_color = self.border_color(cx);         h_flex()            .id(self.id)            .justify_center()            .items_center()            .size(DynamicSpacing::Base20.rems(cx))            .group(group_id.clone())            .child(                div()                    .flex()                    .flex_none()                    .justify_center()                    .items_center()                    .m(DynamicSpacing::Base04.px(cx))                    .size(DynamicSpacing::Base16.rems(cx))                    .rounded_sm()                    .bg(bg_color)                    .border_1()                    .border_color(border_color)                    .when(!self.disabled, |this| {                        this.group_hover(group_id.clone(), |el| {                            el.bg(cx.theme().colors().element_hover)                        })                    })                    .children(icon),            )            .when_some(                self.on_click.filter(|_| !self.disabled),                |this, on_click| {                    this.on_click(move |_, cx| on_click(&self.toggle_state.inverse(), cx))                },            )    }} /// A [`Checkbox`] that has a [`Label`].#[derive(IntoElement)]pub struct CheckboxWithLabel {    id: ElementId,    label: Label,    checked: ToggleState,    on_click: Arc<dyn Fn(&ToggleState, &mut WindowContext) + 'static>,    filled: bool,    style: ToggleStyle,} impl CheckboxWithLabel {    /// Creates a checkbox with an attached label.    pub fn new(        id: impl Into<ElementId>,        label: Label,        checked: ToggleState,        on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,    ) -> Self {        Self {            id: id.into(),            label,            checked,            on_click: Arc::new(on_click),            filled: false,            style: ToggleStyle::default(),        }    }     /// Sets the style of the checkbox using the specified [`ToggleStyle`].    pub fn style(mut self, style: ToggleStyle) -> Self {        self.style = style;        self    }     /// Match the style of the checkbox to the current elevation using [`ToggleStyle::ElevationBased`].    pub fn elevation(mut self, elevation: ElevationIndex) -> Self {        self.style = ToggleStyle::ElevationBased(elevation);        self    }     /// Sets the `fill` setting of the checkbox, indicating whether it should be filled.    pub fn fill(mut self) -> Self {        self.filled = true;        self    }} impl RenderOnce for CheckboxWithLabel {    fn render(self, cx: &mut WindowContext) -> impl IntoElement {        h_flex()            .gap(DynamicSpacing::Base08.rems(cx))            .child(                Checkbox::new(self.id.clone(), self.checked)                    .style(self.style)                    .when(self.filled, Checkbox::fill)                    .on_click({                        let on_click = self.on_click.clone();                        move |checked, cx| {                            (on_click)(checked, cx);                        }                    }),            )            .child(                div()                    .id(SharedString::from(format!("{}-label", self.id)))                    .on_click(move |_event, cx| {                        (self.on_click)(&self.checked.inverse(), cx);                    })                    .child(self.label),            )    }} /// # Switch////// Switches are used to represent opposite states, such as enabled or disabled.#[derive(IntoElement)]pub struct Switch {    id: ElementId,    toggle_state: ToggleState,    disabled: bool,    on_click: Option<Box<dyn Fn(&ToggleState, &mut WindowContext) + 'static>>,} impl Switch {    /// Creates a new [`Switch`].    pub fn new(id: impl Into<ElementId>, state: ToggleState) -> Self {        Self {            id: id.into(),            toggle_state: state,            disabled: false,            on_click: None,        }    }     /// Sets the disabled state of the [`Switch`].    pub fn disabled(mut self, disabled: bool) -> Self {        self.disabled = disabled;        self    }     /// Binds a handler to the [`Switch`] that will be called when clicked.    pub fn on_click(        mut self,        handler: impl Fn(&ToggleState, &mut WindowContext) + 'static,    ) -> Self {        self.on_click = Some(Box::new(handler));        self    }} impl RenderOnce for Switch {    fn render(self, cx: &mut WindowContext) -> impl IntoElement {        let is_on = self.toggle_state == ToggleState::Selected;        let adjust_ratio = if is_light(cx) { 1.5 } else { 1.0 };        let base_color = cx.theme().colors().text;         let bg_color = if is_on {            cx.theme()                .colors()                .element_background                .blend(base_color.opacity(0.08))        } else {            cx.theme().colors().element_background        };        let thumb_color = base_color.opacity(0.8);        let thumb_hover_color = base_color;        let border_color = cx.theme().colors().border_variant;        // Lighter themes need higher contrast borders        let border_hover_color = if is_on {            border_color.blend(base_color.opacity(0.16 * adjust_ratio))        } else {            border_color.blend(base_color.opacity(0.05 * adjust_ratio))        };        let thumb_opacity = match (is_on, self.disabled) {            (_, true) => 0.2,            (true, false) => 1.0,            (false, false) => 0.5,        };         let group_id = format!("switch_group_{:?}", self.id);         h_flex()            .id(self.id)            .items_center()            .w(DynamicSpacing::Base32.rems(cx))            .h(DynamicSpacing::Base20.rems(cx))            .group(group_id.clone())            .child(                h_flex()                    .when(is_on, |on| on.justify_end())                    .when(!is_on, |off| off.justify_start())                    .items_center()                    .size_full()                    .rounded_full()                    .px(DynamicSpacing::Base02.px(cx))                    .bg(bg_color)                    .border_1()                    .border_color(border_color)                    .when(!self.disabled, |this| {                        this.group_hover(group_id.clone(), |el| el.border_color(border_hover_color))                    })                    .child(                        div()                            .size(DynamicSpacing::Base12.rems(cx))                            .rounded_full()                            .bg(thumb_color)                            .when(!self.disabled, |this| {                                this.group_hover(group_id.clone(), |el| el.bg(thumb_hover_color))                            })                            .opacity(thumb_opacity),                    ),            )            .when_some(                self.on_click.filter(|_| !self.disabled),                |this, on_click| {                    this.on_click(move |_, cx| on_click(&self.toggle_state.inverse(), cx))                },            )    }} /// A [`Switch`] that has a [`Label`].#[derive(IntoElement)]pub struct SwitchWithLabel {    id: ElementId,    label: Label,    checked: ToggleState,    on_click: Arc<dyn Fn(&ToggleState, &mut WindowContext) + 'static>,} impl SwitchWithLabel {    /// Creates a switch with an attached label.    pub fn new(        id: impl Into<ElementId>,        label: Label,        checked: ToggleState,        on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,    ) -> Self {        Self {            id: id.into(),            label,            checked,            on_click: Arc::new(on_click),        }    }} impl RenderOnce for SwitchWithLabel {    fn render(self, cx: &mut WindowContext) -> impl IntoElement {        h_flex()            .gap(DynamicSpacing::Base08.rems(cx))            .child(Switch::new(self.id.clone(), self.checked).on_click({                let on_click = self.on_click.clone();                move |checked, cx| {                    (on_click)(checked, cx);                }            }))            .child(                div()                    .id(SharedString::from(format!("{}-label", self.id)))                    .on_click(move |_event, cx| {                        (self.on_click)(&self.checked.inverse(), cx);                    })                    .child(self.label),            )    }} impl ComponentPreview for Checkbox {    fn description() -> impl Into<Option<&'static str>> {        "A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."    }     fn examples(_: &mut WindowContext) -> Vec<ComponentExampleGroup<Self>> {        vec![            example_group_with_title(                "Default",                vec![                    single_example(                        "Unselected",                        Checkbox::new("checkbox_unselected", ToggleState::Unselected),                    ),                    single_example(                        "Indeterminate",                        Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate),                    ),                    single_example(                        "Selected",                        Checkbox::new("checkbox_selected", ToggleState::Selected),                    ),                ],            ),            example_group_with_title(                "Default (Filled)",                vec![                    single_example(                        "Unselected",                        Checkbox::new("checkbox_unselected", ToggleState::Unselected).fill(),                    ),                    single_example(                        "Indeterminate",                        Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate).fill(),                    ),                    single_example(                        "Selected",                        Checkbox::new("checkbox_selected", ToggleState::Selected).fill(),                    ),                ],            ),            example_group_with_title(                "ElevationBased",                vec![                    single_example(                        "Unselected",                        Checkbox::new("checkbox_unfilled_unselected", ToggleState::Unselected)                            .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                    single_example(                        "Indeterminate",                        Checkbox::new(                            "checkbox_unfilled_indeterminate",                            ToggleState::Indeterminate,                        )                        .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                    single_example(                        "Selected",                        Checkbox::new("checkbox_unfilled_selected", ToggleState::Selected)                            .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                ],            ),            example_group_with_title(                "ElevationBased (Filled)",                vec![                    single_example(                        "Unselected",                        Checkbox::new("checkbox_filled_unselected", ToggleState::Unselected)                            .fill()                            .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                    single_example(                        "Indeterminate",                        Checkbox::new("checkbox_filled_indeterminate", ToggleState::Indeterminate)                            .fill()                            .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                    single_example(                        "Selected",                        Checkbox::new("checkbox_filled_selected", ToggleState::Selected)                            .fill()                            .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),                    ),                ],            ),            example_group_with_title(                "Custom Color",

Lines 1–500 of 708.