use kael::{prelude::FluentBuilder as _, *};
use std::rc::Rc;
use crate::theme::use_theme;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ToggleGroupVariant {
#[default]
Single,
Multiple,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ToggleGroupSize {
Sm,
#[default]
Md,
Lg,
}
impl ToggleGroupSize {
fn height(&self) -> Pixels {
match self {
Self::Sm => px(32.0),
Self::Md => px(36.0),
Self::Lg => px(40.0),
}
}
fn px_value(&self) -> Pixels {
match self {
Self::Sm => px(8.0),
Self::Md => px(12.0),
Self::Lg => px(16.0),
}
}
fn text_size(&self) -> Pixels {
match self {
Self::Sm => px(12.0),
Self::Md => px(14.0),
Self::Lg => px(16.0),
}
}
}
#[derive(Clone, Debug)]
pub struct ToggleGroupItem {
value: SharedString,
label: SharedString,
icon: Option<SharedString>,
disabled: bool,
}
impl ToggleGroupItem {
pub fn new(value: impl Into<SharedString>, label: impl Into<SharedString>) -> Self {
Self {
value: value.into(),
label: label.into(),
icon: None,
disabled: false,
}
}
pub fn icon(mut self, icon: impl Into<SharedString>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
#[derive(IntoElement)]
pub struct ToggleGroup {
variant: ToggleGroupVariant,
size: ToggleGroupSize,
items: Vec<ToggleGroupItem>,
value: Option<SharedString>, values: Vec<SharedString>, disabled: bool,
on_change: Option<Rc<dyn Fn(&SharedString, &mut Window, &mut App)>>,
on_multiple_change: Option<Rc<dyn Fn(&Vec<SharedString>, &mut Window, &mut App)>>,
style: StyleRefinement,
}
impl ToggleGroup {
pub fn new() -> Self {
Self {
variant: ToggleGroupVariant::default(),
size: ToggleGroupSize::default(),
items: Vec::new(),
value: None,
values: Vec::new(),
disabled: false,
on_change: None,
on_multiple_change: None,
style: StyleRefinement::default(),
}
}
pub fn variant(mut self, variant: ToggleGroupVariant) -> Self {
self.variant = variant;
self
}
pub fn size(mut self, size: ToggleGroupSize) -> Self {
self.size = size;
self
}
pub fn item(mut self, item: ToggleGroupItem) -> Self {
self.items.push(item);
self
}
pub fn items(mut self, items: Vec<ToggleGroupItem>) -> Self {
self.items.extend(items);
self
}
pub fn value(mut self, value: impl Into<SharedString>) -> Self {
self.value = Some(value.into());
self
}
pub fn values(mut self, values: Vec<SharedString>) -> Self {
self.values = values;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_change<F>(mut self, handler: F) -> Self
where
F: Fn(&SharedString, &mut Window, &mut App) + 'static,
{
self.on_change = Some(Rc::new(handler));
self
}
pub fn on_multiple_change<F>(mut self, handler: F) -> Self
where
F: Fn(&Vec<SharedString>, &mut Window, &mut App) + 'static,
{
self.on_multiple_change = Some(Rc::new(handler));
self
}
}
impl Default for ToggleGroup {
fn default() -> Self {
Self::new()
}
}
impl Styled for ToggleGroup {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for ToggleGroup {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let theme = use_theme();
let variant = self.variant;
let current_value = self.value;
let current_values = self.values;
let disabled = self.disabled;
let size = self.size;
let on_change = self.on_change;
let user_style = self.style;
div()
.flex()
.items_center()
.gap(px(2.0))
.p(px(2.0))
.bg(theme.tokens.muted.opacity(0.3))
.rounded(theme.tokens.radius_md)
.children(self.items.into_iter().map(move |item| {
let is_selected = match variant {
ToggleGroupVariant::Single => current_value.as_ref() == Some(&item.value),
ToggleGroupVariant::Multiple => current_values.contains(&item.value),
};
let is_disabled = disabled || item.disabled;
let value = item.value.clone();
let handler = on_change.clone();
div()
.flex()
.items_center()
.justify_center()
.gap(px(6.0))
.h(size.height())
.px(size.px_value())
.rounded(theme.tokens.radius_sm)
.text_size(size.text_size())
.font_weight(FontWeight::MEDIUM)
.cursor(if is_disabled {
CursorStyle::Arrow
} else {
CursorStyle::PointingHand
})
.when(is_selected, |this: Div| {
this.bg(theme.tokens.background)
.text_color(theme.tokens.foreground)
.shadow(smallvec::smallvec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.05),
offset: point(px(0.0), px(1.0)),
blur_radius: px(2.0),
spread_radius: px(0.0),
inset: false,
}])
})
.when(!is_selected, |this: Div| {
this.text_color(theme.tokens.muted_foreground)
})
.when(is_disabled, |this: Div| this.opacity(0.5))
.when(!is_disabled && !is_selected, |this: Div| {
this.hover(|style| {
style
.bg(theme.tokens.muted.opacity(0.5))
.text_color(theme.tokens.foreground)
})
})
.when(!is_disabled, |this: Div| {
this.on_mouse_down(MouseButton::Left, move |_, window, cx| {
if let Some(h) = &handler {
h(&value, window, cx);
}
})
})
.when_some(item.icon, |this: Div, _icon| {
this
})
.child(item.label)
}))
.map(|this| {
let mut div = this;
div.style().refine(&user_style);
div
})
}
}