use crate::elements::button::Button;
use crate::elements::input::input;
use crate::input::InputState;
use crate::layout::h_stack;
use crate::theme::{ActiveTheme, Themeable};
use crate::traits::disableable::Disableable;
use gpui::{
prelude::FluentBuilder, px, rems, App, Entity, IntoElement, ParentElement, RenderOnce,
SharedString, Styled, Svg, Window,
};
pub fn input_group(input_state: &Entity<InputState>, cx: &App) -> InputGroup {
InputGroup::new(input_state, cx)
}
pub enum InputAddon {
Icon(Svg),
Text(SharedString),
Button(Button),
}
impl InputAddon {
pub fn icon(icon: Svg) -> Self {
InputAddon::Icon(icon)
}
pub fn text(text: impl Into<SharedString>) -> Self {
InputAddon::Text(text.into())
}
pub fn button(button: Button) -> Self {
InputAddon::Button(button)
}
}
#[derive(IntoElement)]
pub struct InputGroup {
input_state: Entity<InputState>,
left_addon: Option<InputAddon>,
right_addon: Option<InputAddon>,
disabled: bool,
}
impl InputGroup {
pub fn new(input_state: &Entity<InputState>, _cx: &App) -> Self {
InputGroup {
input_state: input_state.clone(),
left_addon: None,
right_addon: None,
disabled: false,
}
}
pub fn left_addon(mut self, addon: InputAddon) -> Self {
self.left_addon = Some(addon);
self
}
pub fn right_addon(mut self, addon: InputAddon) -> Self {
self.right_addon = Some(addon);
self
}
}
impl Disableable for InputGroup {
fn is_disabled(&self) -> bool {
self.disabled
}
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
impl RenderOnce for InputGroup {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let has_left = self.left_addon.is_some();
let has_right = self.right_addon.is_some();
let disabled = self.disabled;
h_stack()
.items_center()
.h(px(36.))
.rounded(rems(0.25))
.border_1()
.border_color(theme.input_border())
.bg(theme.input_bg())
.overflow_hidden()
.when(disabled, |el| el.opacity(0.65).cursor_not_allowed())
.when_some(self.left_addon, |el, addon| {
el.child(render_addon(addon, AddonPosition::Left, disabled, cx))
})
.child(
input(&self.input_state, cx)
.border_0()
.bg(gpui::transparent_black())
.flex_1()
.size_full()
.min_w(px(100.))
.when(has_left, |el| el.rounded_l_none())
.when(has_right, |el| el.rounded_r_none()),
)
.when_some(self.right_addon, |el, addon| {
el.child(render_addon(addon, AddonPosition::Right, disabled, cx))
})
}
}
#[derive(Clone, Copy)]
enum AddonPosition {
Left,
Right,
}
fn render_addon(
addon: InputAddon,
position: AddonPosition,
disabled: bool,
cx: &App,
) -> impl IntoElement {
let theme = cx.theme();
match addon {
InputAddon::Icon(icon) => gpui::div()
.flex()
.items_center()
.justify_center()
.px(rems(0.5))
.h_full()
.bg(theme.surface_secondary())
.border_color(theme.border_subtle())
.when(matches!(position, AddonPosition::Left), |el| {
el.border_r_1()
})
.when(matches!(position, AddonPosition::Right), |el| {
el.border_l_1()
})
.child(
icon.size(px(16.))
.text_color(if disabled {
theme.fg_disabled()
} else {
theme.fg_muted()
}),
)
.into_any_element(),
InputAddon::Text(text) => gpui::div()
.flex()
.items_center()
.px(rems(0.5))
.h_full()
.bg(theme.surface_secondary())
.border_color(theme.border_subtle())
.text_sm()
.text_color(if disabled {
theme.fg_disabled()
} else {
theme.fg_muted()
})
.whitespace_nowrap()
.when(matches!(position, AddonPosition::Left), |el| {
el.border_r_1()
})
.when(matches!(position, AddonPosition::Right), |el| {
el.border_l_1()
})
.child(text)
.into_any_element(),
InputAddon::Button(button) => {
gpui::div()
.flex()
.items_center()
.h_full()
.border_color(theme.border_subtle())
.when(matches!(position, AddonPosition::Left), |el| {
el.border_r_1()
})
.when(matches!(position, AddonPosition::Right), |el| {
el.border_l_1()
})
.child(button.disabled(disabled))
.into_any_element()
}
}
}