checkbox.rs

  1#![allow(missing_docs)]
  2
  3use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
  4
  5use crate::prelude::*;
  6use crate::{Color, Icon, IconName, Selection};
  7
  8/// # Checkbox
  9///
 10/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
 11/// Each checkbox works independently from other checkboxes in the list,
 12/// therefore checking an additional box does not affect any other selections.
 13#[derive(IntoElement)]
 14pub struct Checkbox {
 15    id: ElementId,
 16    checked: Selection,
 17    disabled: bool,
 18    on_click: Option<Box<dyn Fn(&Selection, &mut WindowContext) + 'static>>,
 19}
 20
 21impl Checkbox {
 22    pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
 23        Self {
 24            id: id.into(),
 25            checked,
 26            disabled: false,
 27            on_click: None,
 28        }
 29    }
 30
 31    pub fn disabled(mut self, disabled: bool) -> Self {
 32        self.disabled = disabled;
 33        self
 34    }
 35
 36    pub fn on_click(mut self, handler: impl Fn(&Selection, &mut WindowContext) + 'static) -> Self {
 37        self.on_click = Some(Box::new(handler));
 38        self
 39    }
 40}
 41
 42impl RenderOnce for Checkbox {
 43    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
 44        let group_id = format!("checkbox_group_{:?}", self.id);
 45
 46        let icon = match self.checked {
 47            Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
 48                if self.disabled {
 49                    Color::Disabled
 50                } else {
 51                    Color::Selected
 52                },
 53            )),
 54            Selection::Indeterminate => Some(
 55                Icon::new(IconName::Dash)
 56                    .size(IconSize::Small)
 57                    .color(if self.disabled {
 58                        Color::Disabled
 59                    } else {
 60                        Color::Selected
 61                    }),
 62            ),
 63            Selection::Unselected => None,
 64        };
 65
 66        let selected =
 67            self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
 68
 69        let (bg_color, border_color) = match (self.disabled, selected) {
 70            (true, _) => (
 71                cx.theme().colors().ghost_element_disabled,
 72                cx.theme().colors().border_disabled,
 73            ),
 74            (false, true) => (
 75                cx.theme().colors().element_selected,
 76                cx.theme().colors().border,
 77            ),
 78            (false, false) => (
 79                cx.theme().colors().element_background,
 80                cx.theme().colors().border,
 81            ),
 82        };
 83
 84        h_flex()
 85            .id(self.id)
 86            .justify_center()
 87            .items_center()
 88            .size(crate::styles::custom_spacing(cx, 20.))
 89            .group(group_id.clone())
 90            .child(
 91                div()
 92                    .flex()
 93                    .flex_none()
 94                    .justify_center()
 95                    .items_center()
 96                    .m(Spacing::Small.px(cx))
 97                    .size(crate::styles::custom_spacing(cx, 16.))
 98                    .rounded_sm()
 99                    .bg(bg_color)
100                    .border_1()
101                    .border_color(border_color)
102                    .when(!self.disabled, |this| {
103                        this.group_hover(group_id.clone(), |el| {
104                            el.bg(cx.theme().colors().element_hover)
105                        })
106                    })
107                    .children(icon),
108            )
109            .when_some(
110                self.on_click.filter(|_| !self.disabled),
111                |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)),
112            )
113    }
114}