use gpui::prelude::FluentBuilder;
use gpui::*;
use crate::theme::*;
use crate::components::basic::icon::{Icon, IconName};
#[derive(Clone, Debug)]
pub enum CheckboxGroupEvent {
Changed(Vec<String>),
}
impl EventEmitter<CheckboxGroupEvent> for CheckboxGroup {}
#[derive(Clone, Debug)]
pub struct CheckboxOption {
pub value: String,
pub label: String,
}
impl CheckboxOption {
pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
Self {
value: value.into(),
label: label.into(),
}
}
}
pub struct CheckboxGroup {
options: Vec<CheckboxOption>,
selected_values: Vec<String>,
disabled: bool,
size: ComponentSize,
direction: CheckboxGroupDirection,
custom_text_color: Option<Rgba>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum CheckboxGroupDirection {
#[default]
Vertical,
Horizontal,
}
impl CheckboxGroup {
pub fn new(_cx: &mut Context<Self>) -> Self {
Self {
options: Vec::new(),
selected_values: Vec::new(),
disabled: false,
size: ComponentSize::Medium,
direction: CheckboxGroupDirection::Vertical,
custom_text_color: None,
}
}
pub fn options(mut self, options: Vec<CheckboxOption>) -> Self {
self.options = options;
self
}
pub fn values(mut self, values: Vec<String>) -> Self {
self.selected_values = values;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn size(mut self, size: ComponentSize) -> Self {
self.size = size;
self
}
pub fn direction(mut self, direction: CheckboxGroupDirection) -> Self {
self.direction = direction;
self
}
pub fn vertical(mut self) -> Self {
self.direction = CheckboxGroupDirection::Vertical;
self
}
pub fn horizontal(mut self) -> Self {
self.direction = CheckboxGroupDirection::Horizontal;
self
}
pub fn text_color(mut self, color: Rgba) -> Self {
self.custom_text_color = Some(color);
self
}
fn toggle_option(&mut self, value: String, cx: &mut Context<Self>) {
if self.disabled {
return;
}
if let Some(pos) = self.selected_values.iter().position(|v| v == &value) {
self.selected_values.remove(pos);
} else {
self.selected_values.push(value);
}
cx.emit(CheckboxGroupEvent::Changed(self.selected_values.clone()));
cx.notify();
}
}
impl CheckboxGroup {
fn box_size(&self) -> Pixels {
match self.size {
ComponentSize::XSmall => px(14.0),
ComponentSize::Small => px(16.0),
ComponentSize::Medium => px(18.0),
ComponentSize::Large => px(20.0),
ComponentSize::XLarge => px(22.0),
}
}
}
impl Render for CheckboxGroup {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::default();
let disabled = self.disabled;
let direction = self.direction;
let options = self.options.clone();
let selected_values = self.selected_values.clone();
let box_size = self.box_size();
let text_size = self.size.font_size();
let text_color = self.custom_text_color.unwrap_or(theme.colors.text);
div()
.id("checkbox-group")
.flex()
.gap_3()
.when(matches!(direction, CheckboxGroupDirection::Vertical), |this| {
this.flex_col()
})
.when(matches!(direction, CheckboxGroupDirection::Horizontal), |this| {
this.flex_row()
})
.children(options.iter().enumerate().map({
let theme = theme.clone();
let disabled = disabled;
let box_size = box_size;
let text_size = text_size;
let text_color = text_color;
move |(idx, option)| {
let value = option.value.clone();
let label = option.label.clone();
let is_selected = selected_values.contains(&value);
div()
.id(("checkbox-group-item-wrapper", idx))
.on_mouse_down(MouseButton::Left, cx.listener({
let value = value.clone();
move |this, _event: &MouseDownEvent, _window, cx| {
if !this.disabled {
this.toggle_option(value.clone(), cx);
}
}
}))
.child(
div()
.id(("checkbox-group-item", idx))
.flex()
.items_center()
.gap_2()
.cursor(if disabled { CursorStyle::Arrow } else { CursorStyle::PointingHand })
.when(disabled, |this| {
this.opacity(0.64)
})
.child(
div()
.flex()
.items_center()
.justify_center()
.size(box_size)
.rounded(px(4.))
.border_1()
.border_color(if is_selected {
theme.colors.primary
} else {
theme.colors.border
})
.bg(if is_selected {
theme.colors.primary
} else {
rgb(0xFFFFFF)
})
.when(is_selected, |this| {
this.child(
Icon::new(IconName::Check)
.size(crate::components::basic::icon::IconSize::Small)
.color(rgb(0xFFFFFF))
)
})
)
.child(
div()
.text_size(text_size)
.text_color(text_color)
.child(label)
)
)
}
}))
}
}