use gpui::prelude::FluentBuilder;
use gpui::*;
use crate::theme::*;
#[derive(Clone, Debug)]
pub enum RadioGroupEvent {
Changed(String),
}
impl EventEmitter<RadioGroupEvent> for RadioGroup {}
#[derive(Clone, Debug)]
pub struct RadioOption {
pub value: String,
pub label: String,
}
impl RadioOption {
pub fn new(value: impl Into<String>, label: impl Into<String>) -> Self {
Self {
value: value.into(),
label: label.into(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum RadioGroupDirection {
#[default]
Vertical,
Horizontal,
}
pub struct RadioGroup {
options: Vec<RadioOption>,
selected_value: Option<String>,
disabled: bool,
size: ComponentSize,
direction: RadioGroupDirection,
custom_text_color: Option<Rgba>,
}
impl RadioGroup {
pub fn new(_cx: &mut Context<Self>) -> Self {
Self {
options: Vec::new(),
selected_value: None,
disabled: false,
size: ComponentSize::Medium,
direction: RadioGroupDirection::Vertical,
custom_text_color: None,
}
}
pub fn options(mut self, options: Vec<RadioOption>) -> Self {
self.options = options;
self
}
pub fn value(mut self, value: impl Into<String>) -> Self {
self.selected_value = Some(value.into());
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: RadioGroupDirection) -> Self {
self.direction = direction;
self
}
pub fn vertical(mut self) -> Self {
self.direction = RadioGroupDirection::Vertical;
self
}
pub fn horizontal(mut self) -> Self {
self.direction = RadioGroupDirection::Horizontal;
self
}
pub fn text_color(mut self, color: Rgba) -> Self {
self.custom_text_color = Some(color);
self
}
fn select_option(&mut self, value: String, cx: &mut Context<Self>) {
if self.disabled {
return;
}
self.selected_value = Some(value.clone());
cx.emit(RadioGroupEvent::Changed(value));
cx.notify();
}
fn is_selected(&self, value: &str) -> bool {
self.selected_value.as_ref() == Some(&value.to_string())
}
fn radio_size(&self) -> f32 {
match self.size {
ComponentSize::XSmall => 14.0,
ComponentSize::Small => 16.0,
ComponentSize::Medium => 18.0,
ComponentSize::Large => 20.0,
ComponentSize::XLarge => 22.0,
}
}
fn render_radio(&self, option: &RadioOption, index: usize, theme: &Theme) -> impl IntoElement {
let value = option.value.clone();
let label = option.label.clone();
let is_selected = self.is_selected(&value);
let disabled = self.disabled;
let radio_size_val = self.radio_size();
let radio_size = px(radio_size_val);
let text_size = self.size.font_size();
let text_color = self.custom_text_color.unwrap_or(theme.colors.text);
div()
.id(("radio-item", index))
.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(radio_size)
.rounded(px(radio_size_val / 2.0)) .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(
div()
.size(px(radio_size_val * 0.5))
.rounded(px(radio_size_val * 0.25))
.bg(rgb(0xFFFFFF))
)
})
)
.child(
div()
.text_size(text_size)
.text_color(text_color)
.child(label)
)
}
}
impl Render for RadioGroup {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::default();
let options = self.options.clone();
let direction = self.direction;
div()
.id("radio-group")
.flex()
.gap_3()
.when(matches!(direction, RadioGroupDirection::Vertical), |this| {
this.flex_col()
})
.when(matches!(direction, RadioGroupDirection::Horizontal), |this| {
this.flex_row()
})
.children(options.iter().enumerate().map({
let theme = theme.clone();
move |(idx, option)| {
let value = option.value.clone();
let option_clone = option.clone();
div()
.id(("radio-group-item", idx))
.on_mouse_down(MouseButton::Left, cx.listener({
let value = value.clone();
move |this, _event: &MouseDownEvent, _window, cx| {
if !this.disabled {
this.select_option(value.clone(), cx);
}
}
}))
.child(self.render_radio(&option_clone, idx, &theme))
}
}))
}
}