use crate::theme::{ActiveTheme, Themeable};
use crate::traits::disableable::Disableable;
use crate::traits::orientable::{Orientable, Orientation};
use gpui::{
div, prelude::*, rems, Context, ElementId, EventEmitter, InteractiveElement, IntoElement,
MouseButton, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
};
pub struct RadioGroupChanged<T: Clone> {
pub value: T,
}
#[derive(Clone)]
pub struct RadioOption<T: Clone> {
pub value: T,
pub label: SharedString,
pub disabled: bool,
}
impl<T: Clone> RadioOption<T> {
pub fn new(value: T, label: impl Into<SharedString>) -> Self {
Self {
value,
label: label.into(),
disabled: false,
}
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
pub struct RadioGroup<T: Clone + PartialEq + 'static> {
id: ElementId,
options: Vec<RadioOption<T>>,
selected: Option<T>,
disabled: bool,
orientation: Orientation,
}
impl<T: Clone + PartialEq + 'static> EventEmitter<RadioGroupChanged<T>> for RadioGroup<T> {}
impl<T: Clone + PartialEq + 'static> RadioGroup<T> {
pub fn new(id: impl Into<ElementId>, options: Vec<RadioOption<T>>) -> Self {
Self {
id: id.into(),
options,
selected: None,
disabled: false,
orientation: Orientation::default(),
}
}
pub fn selected(mut self, value: T) -> Self {
self.selected = Some(value);
self
}
pub fn selected_option(mut self, value: Option<T>) -> Self {
self.selected = value;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn get_selected(&self) -> Option<&T> {
self.selected.as_ref()
}
pub fn set_selected(&mut self, value: T, cx: &mut Context<Self>) {
if self.selected.as_ref() != Some(&value) {
self.selected = Some(value.clone());
cx.emit(RadioGroupChanged { value });
cx.notify();
}
}
fn select_option(&mut self, index: usize, cx: &mut Context<Self>) {
if let Some(option) = self.options.get(index) {
if !option.disabled && !self.disabled {
self.set_selected(option.value.clone(), cx);
}
}
}
}
impl<T: Clone + PartialEq + 'static> Render for RadioGroup<T> {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.theme();
let group_disabled = self.disabled;
let selected = self.selected.clone();
let orientation = self.orientation;
let radio_size = rems(1.0);
let dot_size = rems(0.5);
let container = if orientation == Orientation::Vertical {
div().flex().flex_col().gap(rems(0.5))
} else {
div().flex().flex_row().gap(rems(1.0)).items_center()
};
container.id(self.id.clone()).children(
self.options
.iter()
.enumerate()
.map(|(index, option)| {
let is_selected = selected.as_ref() == Some(&option.value);
let is_disabled = group_disabled || option.disabled;
let label = option.label.clone();
let radio_bg = if is_disabled {
theme.surface_tertiary()
} else {
theme.surface()
};
let radio_border = if is_disabled {
theme.border_subtle()
} else if is_selected {
theme.accent()
} else {
theme.border()
};
let dot_color = if is_disabled {
theme.fg_disabled()
} else {
theme.accent()
};
let text_color = if is_disabled {
theme.fg_disabled()
} else {
theme.fg()
};
div()
.id(ElementId::NamedInteger("radio-option".into(), index as u64))
.flex()
.flex_row()
.gap(rems(0.5))
.items_center()
.when(!is_disabled, |this| {
this.cursor_pointer()
.on_mouse_down(MouseButton::Left, |_, window, _| {
window.prevent_default()
})
.on_click(cx.listener(move |this, _, _, cx| {
this.select_option(index, cx);
}))
})
.when(is_disabled, |this| this.cursor_not_allowed().opacity(0.65))
.child(
div()
.size(radio_size)
.flex_none()
.flex()
.items_center()
.justify_center()
.bg(radio_bg)
.border_1()
.border_color(radio_border)
.rounded_full()
.when(!is_disabled, |this| {
this.hover(|style| {
style.border_color(if is_selected {
theme.accent()
} else {
theme.border_secondary()
})
})
})
.when(is_selected, |this| {
this.child(div().size(dot_size).bg(dot_color).rounded_full())
}),
)
.child(div().text_sm().text_color(text_color).child(label))
})
.collect::<Vec<_>>(),
)
}
}
pub fn radio_group<T: Clone + PartialEq + 'static>(
id: impl Into<ElementId>,
options: Vec<RadioOption<T>>,
) -> RadioGroup<T> {
RadioGroup::new(id, options)
}
pub fn radio_option<T: Clone>(value: T, label: impl Into<SharedString>) -> RadioOption<T> {
RadioOption::new(value, label)
}
impl<T: Clone + PartialEq + 'static> Disableable for RadioGroup<T> {
fn is_disabled(&self) -> bool {
self.disabled
}
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl<T: Clone> Disableable for RadioOption<T> {
fn is_disabled(&self) -> bool {
self.disabled
}
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl<T: Clone + PartialEq + 'static> Orientable for RadioGroup<T> {
fn orientation(mut self, orientation: Orientation) -> Self {
self.orientation = orientation;
self
}
}