use gpui::{
AnyElement, App, Axis, Div, InteractiveElement as _, IntoElement, ParentElement, SharedString,
Stateful, Styled, Window, div, prelude::FluentBuilder as _,
};
use std::{any::TypeId, ops::Deref, rc::Rc};
use crate::{
ActiveTheme as _, AxisExt, StyledExt as _,
label::Label,
setting::{
AnySettingField, ElementField, RenderOptions,
fields::{BoolField, DropdownField, NumberField, SettingFieldRender, StringField},
},
text::Text,
v_flex,
};
#[derive(Clone)]
pub enum SettingItem {
Item {
title: SharedString,
description: Option<Text>,
layout: Axis,
field: Rc<dyn AnySettingField>,
},
Element {
render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static>,
},
}
impl SettingItem {
pub fn new<F>(title: impl Into<SharedString>, field: F) -> Self
where
F: AnySettingField + 'static,
{
SettingItem::Item {
title: title.into(),
description: None,
layout: Axis::Horizontal,
field: Rc::new(field),
}
}
pub fn render<R, E>(render: R) -> Self
where
E: IntoElement,
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
{
SettingItem::Element {
render: Rc::new(move |options, window, cx| {
render(options, window, cx).into_any_element()
}),
}
}
pub fn description(mut self, description: impl Into<Text>) -> Self {
match &mut self {
SettingItem::Item { description: d, .. } => {
*d = Some(description.into());
}
SettingItem::Element { .. } => {}
}
self
}
pub fn layout(mut self, layout: Axis) -> Self {
match &mut self {
SettingItem::Item { layout: l, .. } => {
*l = layout;
}
SettingItem::Element { .. } => {}
}
self
}
pub(crate) fn is_match(&self, query: &str) -> bool {
match self {
SettingItem::Item {
title, description, ..
} => {
title.to_lowercase().contains(&query.to_lowercase())
|| description.as_ref().map_or(false, |d| {
d.as_str().to_lowercase().contains(&query.to_lowercase())
})
}
SettingItem::Element { .. } => query.is_empty(),
}
}
pub(crate) fn is_resettable(&self, cx: &App) -> bool {
match self {
SettingItem::Item { field, .. } => field.is_resettable(cx),
SettingItem::Element { .. } => false,
}
}
pub(crate) fn reset(&self, window: &mut Window, cx: &mut App) {
match self {
SettingItem::Item { field, .. } => field.reset(window, cx),
SettingItem::Element { .. } => {}
}
}
fn render_field(
field: Rc<dyn AnySettingField>,
options: RenderOptions,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
let field_type = field.field_type();
let style = field.style().clone();
let type_id = field.deref().type_id();
let renderer: Box<dyn SettingFieldRender> = match type_id {
t if t == std::any::TypeId::of::<bool>() => {
Box::new(BoolField::new(field_type.is_switch()))
}
t if t == TypeId::of::<f64>() && field_type.is_number_input() => {
Box::new(NumberField::new(field_type.number_input_options()))
}
t if t == TypeId::of::<SharedString>() && field_type.is_input() => {
Box::new(StringField::<SharedString>::new())
}
t if t == TypeId::of::<String>() && field_type.is_input() => {
Box::new(StringField::<String>::new())
}
t if t == TypeId::of::<SharedString>() && field_type.is_dropdown() => Box::new(
DropdownField::<SharedString>::new(field_type.dropdown_options()),
),
t if t == TypeId::of::<String>() && field_type.is_dropdown() => {
Box::new(DropdownField::<String>::new(field_type.dropdown_options()))
}
_ if field_type.is_element() => Box::new(ElementField::new(field_type.element())),
_ => unimplemented!("Unsupported setting type: {}", field.deref().type_name()),
};
renderer.render(field, &options, &style, window, cx)
}
pub(super) fn render_item(
self,
options: &RenderOptions,
window: &mut Window,
cx: &mut App,
) -> Stateful<Div> {
div()
.id(SharedString::from(format!("item-{}", options.item_ix)))
.w_full()
.child(match self {
SettingItem::Item {
title,
description,
layout,
field,
} => div()
.w_full()
.overflow_hidden()
.map(|this| {
if layout.is_horizontal() {
this.h_flex().justify_between().items_start()
} else {
this.v_flex()
}
})
.gap_3()
.child(
v_flex()
.map(|this| {
if layout.is_horizontal() {
this.flex_1().max_w_3_5()
} else {
this.w_full()
}
})
.gap_1()
.child(Label::new(title.clone()).text_sm())
.when_some(description.clone(), |this, description| {
this.child(
div()
.size_full()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child(description),
)
}),
)
.child(div().id("field").child(Self::render_field(
field,
RenderOptions { layout, ..*options },
window,
cx,
)))
.into_any_element(),
SettingItem::Element { render } => {
(render)(&options, window, cx).into_any_element()
}
})
}
}