## Break Changes
- Refactor `Toggle` new API to require a `id`, and renamed `on_change`
to `on_click.
- Renamed `on_change` method to `on_click` for `ToggleGroup`.
```diff
- Toggle::label("Hello").id("hello").on_change(..)
+ Toggle::new("hello).label("Hello").on_click(..)
- ToggleGroup::new("group1).on_change(..)
+ ToggleGroup::new("group1).on_click(..)
```
280 lines
8 KiB
Rust
280 lines
8 KiB
Rust
use std::{cell::Cell, rc::Rc};
|
|
|
|
use gpui::{
|
|
div, prelude::FluentBuilder as _, AnyElement, App, ElementId, InteractiveElement, IntoElement,
|
|
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
|
|
Window,
|
|
};
|
|
use smallvec::{smallvec, SmallVec};
|
|
|
|
use crate::{h_flex, ActiveTheme, Disableable, Icon, Sizable, Size, StyledExt};
|
|
|
|
#[derive(Default, Copy, Debug, Clone, PartialEq, Eq, Hash)]
|
|
pub enum ToggleVariant {
|
|
#[default]
|
|
Ghost,
|
|
Outline,
|
|
}
|
|
|
|
pub trait ToggleVariants: Sized {
|
|
/// Set the variant of the toggle.
|
|
fn with_variant(self, variant: ToggleVariant) -> Self;
|
|
/// Set the variant to ghost.
|
|
fn ghost(self) -> Self {
|
|
self.with_variant(ToggleVariant::Ghost)
|
|
}
|
|
/// Set the variant to outline.
|
|
fn outline(self) -> Self {
|
|
self.with_variant(ToggleVariant::Outline)
|
|
}
|
|
}
|
|
|
|
#[derive(IntoElement)]
|
|
pub struct Toggle {
|
|
id: ElementId,
|
|
style: StyleRefinement,
|
|
checked: bool,
|
|
size: Size,
|
|
variant: ToggleVariant,
|
|
disabled: bool,
|
|
children: SmallVec<[AnyElement; 1]>,
|
|
on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
|
}
|
|
|
|
impl Toggle {
|
|
/// Create a new Toggle element.
|
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
style: StyleRefinement::default(),
|
|
checked: false,
|
|
size: Size::default(),
|
|
variant: ToggleVariant::default(),
|
|
disabled: false,
|
|
children: smallvec![],
|
|
on_click: None,
|
|
}
|
|
}
|
|
|
|
/// Add a label to the toggle.
|
|
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
|
let label: SharedString = label.into();
|
|
self.children.push(label.into_any_element());
|
|
self
|
|
}
|
|
|
|
/// Add icon to the toggle.
|
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
|
let icon: Icon = icon.into();
|
|
self.children.push(icon.into());
|
|
self
|
|
}
|
|
|
|
/// Set the checked state of the toggle, default: false
|
|
pub fn checked(mut self, checked: bool) -> Self {
|
|
self.checked = checked;
|
|
self
|
|
}
|
|
|
|
/// Set the callback to be called when the toggle is clicked.
|
|
///
|
|
/// The `&bool` parameter represents the new checked state of the toggle.
|
|
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
|
|
self.on_click = Some(Box::new(handler));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl ToggleVariants for Toggle {
|
|
fn with_variant(mut self, variant: ToggleVariant) -> Self {
|
|
self.variant = variant;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl ParentElement for Toggle {
|
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
|
self.children.extend(elements);
|
|
}
|
|
}
|
|
|
|
impl Disableable for Toggle {
|
|
fn disabled(mut self, disabled: bool) -> Self {
|
|
self.disabled = disabled;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Sizable for Toggle {
|
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
|
self.size = size.into();
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Styled for Toggle {
|
|
fn style(&mut self) -> &mut StyleRefinement {
|
|
&mut self.style
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for Toggle {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let checked = self.checked;
|
|
let disabled = self.disabled;
|
|
let hoverable = !disabled && !checked;
|
|
|
|
div()
|
|
.id(self.id)
|
|
.flex()
|
|
.flex_row()
|
|
.items_center()
|
|
.justify_center()
|
|
.map(|this| match self.size {
|
|
Size::XSmall => this.min_w_5().h_5().px_0p5().text_xs(),
|
|
Size::Small => this.min_w_6().h_6().px_1().text_sm(),
|
|
Size::Large => this.min_w_9().h_9().px_3().text_lg(),
|
|
_ => this.min_w_8().h_8().px_2(),
|
|
})
|
|
.rounded(cx.theme().radius)
|
|
.when(self.variant == ToggleVariant::Outline, |this| {
|
|
this.border_1()
|
|
.border_color(cx.theme().border)
|
|
.bg(cx.theme().background)
|
|
.when(cx.theme().shadow, |this| this.shadow_xs())
|
|
})
|
|
.when(hoverable, |this| {
|
|
this.hover(|this| {
|
|
this.bg(cx.theme().accent)
|
|
.text_color(cx.theme().accent_foreground)
|
|
})
|
|
})
|
|
.when(checked, |this| {
|
|
this.bg(cx.theme().accent)
|
|
.text_color(cx.theme().accent_foreground)
|
|
})
|
|
.refine_style(&self.style)
|
|
.children(self.children)
|
|
.when(!disabled, |this| {
|
|
this.when_some(self.on_click, |this, on_click| {
|
|
this.on_click(move |_, window, cx| on_click(&!checked, window, cx))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
/// A group of toggles.
|
|
#[derive(IntoElement)]
|
|
pub struct ToggleGroup {
|
|
id: ElementId,
|
|
style: StyleRefinement,
|
|
size: Size,
|
|
variant: ToggleVariant,
|
|
disabled: bool,
|
|
items: Vec<Toggle>,
|
|
on_click: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>,
|
|
}
|
|
|
|
impl ToggleGroup {
|
|
/// Create a new ToggleGroup element.
|
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
|
Self {
|
|
id: id.into(),
|
|
style: StyleRefinement::default(),
|
|
size: Size::default(),
|
|
variant: ToggleVariant::default(),
|
|
disabled: false,
|
|
items: Vec::new(),
|
|
on_click: None,
|
|
}
|
|
}
|
|
|
|
/// Add a child [`Toggle`] to the group.
|
|
pub fn child(mut self, toggle: impl Into<Toggle>) -> Self {
|
|
self.items.push(toggle.into());
|
|
self
|
|
}
|
|
|
|
/// Add multiple [`Toggle`]s to the group.
|
|
pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Toggle>>) -> Self {
|
|
self.items.extend(children.into_iter().map(Into::into));
|
|
self
|
|
}
|
|
|
|
/// Set the callback to be called when the toggle group changes.
|
|
///
|
|
/// The `&Vec<bool>` parameter represents the new check state of each [`Toggle`] in the group.
|
|
pub fn on_click(
|
|
mut self,
|
|
on_click: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static,
|
|
) -> Self {
|
|
self.on_click = Some(Rc::new(on_click));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Sizable for ToggleGroup {
|
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
|
self.size = size.into();
|
|
self
|
|
}
|
|
}
|
|
|
|
impl ToggleVariants for ToggleGroup {
|
|
fn with_variant(mut self, variant: ToggleVariant) -> Self {
|
|
self.variant = variant;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Disableable for ToggleGroup {
|
|
fn disabled(mut self, disabled: bool) -> Self {
|
|
self.disabled = disabled;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Styled for ToggleGroup {
|
|
fn style(&mut self) -> &mut StyleRefinement {
|
|
&mut self.style
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for ToggleGroup {
|
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
|
let disabled = self.disabled;
|
|
let checks = self
|
|
.items
|
|
.iter()
|
|
.map(|item| item.checked)
|
|
.collect::<Vec<bool>>();
|
|
let state = Rc::new(Cell::new(None));
|
|
|
|
h_flex()
|
|
.id(self.id)
|
|
.gap_1()
|
|
.refine_style(&self.style)
|
|
.children(self.items.into_iter().enumerate().map({
|
|
|(ix, item)| {
|
|
let state = state.clone();
|
|
item.disabled(disabled)
|
|
.with_size(self.size)
|
|
.with_variant(self.variant)
|
|
.on_click(move |_, _, _| {
|
|
state.set(Some(ix));
|
|
})
|
|
}
|
|
}))
|
|
.when(!disabled, |this| {
|
|
this.when_some(self.on_click, |this, on_click| {
|
|
this.on_click(move |_, window, cx| {
|
|
if let Some(ix) = state.get() {
|
|
let mut checks = checks.clone();
|
|
checks[ix] = !checks[ix];
|
|
on_click(&checks, window, cx);
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
}
|