toggle: Add Toggle and ToggleGroup. (#691)
- Improve `Icon` to inherit parent element font size by default. <img width="777" alt="image" src="https://github.com/user-attachments/assets/1a0ee94e-46ef-4b02-9538-09880586d3f2" /> <img width="777" alt="image" src="https://github.com/user-attachments/assets/4ecb016f-2e7c-4266-9a25-eae2566b2844" />
This commit is contained in:
parent
d009b37bf1
commit
c9c6449d24
10 changed files with 612 additions and 14 deletions
|
|
@ -37,6 +37,7 @@ UI components for building fantastic desktop application by using [GPUI](https:/
|
|||
- Breadcrumb
|
||||
- Badge
|
||||
- TextView (Markdown, Simple HTML) to native rendering, syntax highlighting.
|
||||
- Toggle, ToggleGroup
|
||||
|
||||
## Showcase
|
||||
|
||||
|
|
|
|||
35
crates/story/examples/button.rs
Normal file
35
crates/story/examples/button.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use gpui::*;
|
||||
use story::{Assets, ButtonStory};
|
||||
|
||||
pub struct Example {
|
||||
root: Entity<ButtonStory>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let root = ButtonStory::view(window, cx);
|
||||
|
||||
Self { root }
|
||||
}
|
||||
|
||||
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Example {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().p_4().size_full().child(self.root.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = Application::new().with_assets(Assets);
|
||||
|
||||
app.run(move |cx| {
|
||||
story::init(cx);
|
||||
cx.activate(true);
|
||||
|
||||
story::create_new_window("Button Example", Example::view, cx);
|
||||
});
|
||||
}
|
||||
35
crates/story/examples/toggle.rs
Normal file
35
crates/story/examples/toggle.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use gpui::*;
|
||||
use story::{Assets, ToggleStory};
|
||||
|
||||
pub struct Example {
|
||||
root: Entity<ToggleStory>,
|
||||
}
|
||||
|
||||
impl Example {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let root = ToggleStory::view(window, cx);
|
||||
|
||||
Self { root }
|
||||
}
|
||||
|
||||
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Example {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div().p_4().size_full().child(self.root.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = Application::new().with_assets(Assets);
|
||||
|
||||
app.run(move |cx| {
|
||||
story::init(cx);
|
||||
cx.activate(true);
|
||||
|
||||
story::create_new_window("Toggle Example", Example::view, cx);
|
||||
});
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ mod table_story;
|
|||
mod tabs_story;
|
||||
mod text_story;
|
||||
mod title_bar;
|
||||
mod toggle_story;
|
||||
mod tooltip_story;
|
||||
mod webview_story;
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ pub use calendar_story::CalendarStory;
|
|||
pub use dropdown_story::DropdownStory;
|
||||
pub use form_story::FormStory;
|
||||
pub use tabs_story::TabsStory;
|
||||
pub use toggle_story::ToggleStory;
|
||||
|
||||
use gpui::{
|
||||
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
||||
|
|
|
|||
208
crates/story/src/toggle_story.rs
Normal file
208
crates/story/src/toggle_story.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
use gpui::{
|
||||
actions, App, AppContext as _, Context, Entity, Focusable, IntoElement, ParentElement as _,
|
||||
Render, Styled as _, Window,
|
||||
};
|
||||
|
||||
use gpui_component::{
|
||||
button::{Toggle, ToggleGroup, ToggleVariants},
|
||||
h_flex, v_flex, IconName, Sizable,
|
||||
};
|
||||
|
||||
actions!(button_story, [Disabled, Loading, Selected, Compact]);
|
||||
|
||||
pub struct ToggleStory {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
single_toggle: usize,
|
||||
checked: Vec<bool>,
|
||||
}
|
||||
|
||||
impl ToggleStory {
|
||||
pub fn view(_: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
single_toggle: 0,
|
||||
checked: vec![false; 20],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Story for ToggleStory {
|
||||
fn title() -> &'static str {
|
||||
"ToggleButton"
|
||||
}
|
||||
|
||||
fn description() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
fn closable() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
|
||||
Self::view(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for ToggleStory {
|
||||
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ToggleStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Toggle::label("Single Toggle Item 1")
|
||||
.id("single-toggle-item-1")
|
||||
.large()
|
||||
.checked(self.single_toggle == 1)
|
||||
.on_change(cx.listener(|view, checked, _, cx| {
|
||||
if *checked {
|
||||
view.single_toggle = 1;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Toggle::label("Single Toggle Item 2")
|
||||
.id("single-toggle-item-2")
|
||||
.large()
|
||||
.checked(self.single_toggle == 2)
|
||||
.on_change(cx.listener(|view, checked, _, cx| {
|
||||
if *checked {
|
||||
view.single_toggle = 2;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Toggle::icon(IconName::Eye)
|
||||
.id("single-toggle-item-3")
|
||||
.large()
|
||||
.checked(self.single_toggle == 3)
|
||||
.on_change(cx.listener(|view, checked, _, cx| {
|
||||
if *checked {
|
||||
view.single_toggle = 3;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_5()
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group1")
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group1-sm")
|
||||
.small()
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
view.checked[4] = checkeds[4];
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group1-xs")
|
||||
.xsmall()
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
view.checked[4] = checkeds[4];
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_5()
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group2")
|
||||
.outline()
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
view.checked[4] = checkeds[4];
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group2-sm")
|
||||
.outline()
|
||||
.small()
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
view.checked[4] = checkeds[4];
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
ToggleGroup::new("toggle-button-group2-xs")
|
||||
.outline()
|
||||
.xsmall()
|
||||
.child(Toggle::icon(IconName::Bell).checked(self.checked[0]))
|
||||
.child(Toggle::icon(IconName::Bot).checked(self.checked[1]))
|
||||
.child(Toggle::icon(IconName::Inbox).checked(self.checked[2]))
|
||||
.child(Toggle::icon(IconName::Check).checked(self.checked[3]))
|
||||
.child(Toggle::label("Other").checked(self.checked[4]))
|
||||
.on_change(cx.listener(|view, checkeds: &Vec<bool>, _, cx| {
|
||||
view.checked[0] = checkeds[0];
|
||||
view.checked[1] = checkeds[1];
|
||||
view.checked[2] = checkeds[2];
|
||||
view.checked[3] = checkeds[3];
|
||||
view.checked[4] = checkeds[4];
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -611,19 +611,16 @@ impl ButtonVariant {
|
|||
fn selected(&self, cx: &mut App) -> ButtonVariantStyle {
|
||||
let bg = match self {
|
||||
ButtonVariant::Primary => cx.theme().primary_active,
|
||||
ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_active,
|
||||
ButtonVariant::Ghost => {
|
||||
if cx.theme().mode.is_dark() {
|
||||
cx.theme().secondary.lighten(0.2).opacity(0.8)
|
||||
} else {
|
||||
cx.theme().secondary.darken(0.2).opacity(0.8)
|
||||
}
|
||||
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
||||
cx.theme().secondary_active
|
||||
}
|
||||
ButtonVariant::Danger => cx.theme().danger_active,
|
||||
ButtonVariant::Link => cx.theme().transparent,
|
||||
ButtonVariant::Text => cx.theme().transparent,
|
||||
ButtonVariant::Custom(colors) => colors.active,
|
||||
};
|
||||
}
|
||||
.opacity(0.5);
|
||||
|
||||
let border = self.border_color(cx);
|
||||
let fg = match self {
|
||||
ButtonVariant::Link => cx.theme().link_active,
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ pub struct ButtonGroup {
|
|||
pub base: Div,
|
||||
id: ElementId,
|
||||
children: Vec<Button>,
|
||||
multiple: bool,
|
||||
disabled: bool,
|
||||
pub(super) multiple: bool,
|
||||
pub(super) disabled: bool,
|
||||
|
||||
// The button props
|
||||
compact: Option<bool>,
|
||||
variant: Option<ButtonVariant>,
|
||||
size: Option<Size>,
|
||||
pub(super) compact: Option<bool>,
|
||||
pub(super) variant: Option<ButtonVariant>,
|
||||
pub(super) size: Option<Size>,
|
||||
|
||||
on_click: Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
|
@ -55,6 +55,12 @@ impl ButtonGroup {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds multiple buttons as children to the ButtonGroup.
|
||||
pub fn children(mut self, children: impl IntoIterator<Item = Button>) -> Self {
|
||||
self.children.extend(children);
|
||||
self
|
||||
}
|
||||
|
||||
/// With the multiple selection mode.
|
||||
pub fn multiple(mut self, multiple: bool) -> Self {
|
||||
self.multiple = multiple;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
mod button;
|
||||
mod button_group;
|
||||
mod dropdown_button;
|
||||
mod toggle;
|
||||
|
||||
pub use button::*;
|
||||
pub use button_group::*;
|
||||
pub use dropdown_button::*;
|
||||
pub use toggle::*;
|
||||
|
|
|
|||
308
crates/ui/src/button/toggle.rs
Normal file
308
crates/ui/src/button/toggle.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId, InteractiveElement,
|
||||
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled as _,
|
||||
Window,
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::{h_flex, ActiveTheme, Disableable, Icon, Sizable, Size};
|
||||
|
||||
#[derive(Default, Copy, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ToggleVariant {
|
||||
#[default]
|
||||
Ghost,
|
||||
Outline,
|
||||
}
|
||||
|
||||
pub trait ToggleVariants: Sized {
|
||||
fn with_variant(self, variant: ToggleVariant) -> Self;
|
||||
fn ghost(self) -> Self {
|
||||
self.with_variant(ToggleVariant::Ghost)
|
||||
}
|
||||
fn outline(self) -> Self {
|
||||
self.with_variant(ToggleVariant::Outline)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Toggle {
|
||||
base: Div,
|
||||
checked: bool,
|
||||
size: Size,
|
||||
variant: ToggleVariant,
|
||||
disabled: bool,
|
||||
children: SmallVec<[AnyElement; 1]>,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct InteractiveToggle {
|
||||
id: ElementId,
|
||||
toggle: Toggle,
|
||||
on_change: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl Toggle {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
checked: false,
|
||||
size: Size::default(),
|
||||
variant: ToggleVariant::default(),
|
||||
disabled: false,
|
||||
children: smallvec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(label: impl Into<SharedString>) -> Self {
|
||||
Self::new().child(label.into())
|
||||
}
|
||||
|
||||
pub fn icon(icon: impl Into<Icon>) -> Self {
|
||||
Self::new().child(icon.into())
|
||||
}
|
||||
|
||||
pub fn id(self, id: impl Into<ElementId>) -> InteractiveToggle {
|
||||
InteractiveToggle {
|
||||
id: id.into(),
|
||||
toggle: self,
|
||||
on_change: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
self.checked = checked;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleVariants for Toggle {
|
||||
fn with_variant(mut self, variant: ToggleVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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 ParentElement for Toggle {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Toggle {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl gpui::IntoElement {
|
||||
let checked = self.checked;
|
||||
let disabled = self.disabled;
|
||||
let hoverable = !disabled && !checked;
|
||||
|
||||
self.base
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.cursor_pointer()
|
||||
.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_sm())
|
||||
})
|
||||
.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)
|
||||
})
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveToggle {
|
||||
/// Sets the callback to be invoked when the toggle is clicked.
|
||||
///
|
||||
/// The first argument is a boolean indicating whether the toggle is checked.
|
||||
pub fn on_change(mut self, on_change: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_change = Some(Box::new(on_change));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
self.toggle.checked = checked;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sizable for InteractiveToggle {
|
||||
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||
self.toggle = self.toggle.with_size(size);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToggleVariants for InteractiveToggle {
|
||||
fn with_variant(mut self, variant: ToggleVariant) -> Self {
|
||||
self.toggle.variant = variant;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Disableable for InteractiveToggle {
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.toggle.disabled = disabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for InteractiveToggle {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.toggle.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for InteractiveToggle {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl gpui::IntoElement {
|
||||
let checked = self.toggle.checked;
|
||||
let disabled = self.toggle.disabled;
|
||||
|
||||
div()
|
||||
.id(self.id)
|
||||
.child(self.toggle)
|
||||
.when(!disabled, |this| {
|
||||
this.when_some(self.on_change, |this, on_change| {
|
||||
this.on_click(move |_, window, cx| on_change(&!checked, window, cx))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ToggleGroup {
|
||||
id: ElementId,
|
||||
size: Size,
|
||||
variant: ToggleVariant,
|
||||
disabled: bool,
|
||||
items: Vec<Toggle>,
|
||||
on_change: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>,
|
||||
}
|
||||
|
||||
impl ToggleGroup {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
size: Size::default(),
|
||||
variant: ToggleVariant::default(),
|
||||
disabled: false,
|
||||
items: Vec::new(),
|
||||
on_change: 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 check state of each [`Toggle`] in the group.
|
||||
pub fn on_change(
|
||||
mut self,
|
||||
on_change: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_change = Some(Rc::new(on_change));
|
||||
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 RenderOnce for ToggleGroup {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl gpui::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()
|
||||
.children(self.items.into_iter().enumerate().map({
|
||||
|(ix, item)| {
|
||||
let state = state.clone();
|
||||
item.disabled(disabled)
|
||||
.id(ix)
|
||||
.with_size(self.size)
|
||||
.with_variant(self.variant)
|
||||
.on_change(move |_, _, _| {
|
||||
state.set(Some(ix));
|
||||
})
|
||||
}
|
||||
}))
|
||||
.when(!disabled, |this| {
|
||||
this.when_some(self.on_change, |this, on_change| {
|
||||
this.on_click(move |_, window, cx| {
|
||||
if let Some(ix) = state.get() {
|
||||
let mut checks = checks.clone();
|
||||
checks[ix] = !checks[ix];
|
||||
on_change(&checks, window, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -280,9 +280,11 @@ impl Sizable for Icon {
|
|||
impl RenderOnce for Icon {
|
||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let text_color = self.text_color.unwrap_or_else(|| window.text_style().color);
|
||||
let text_size = window.text_style().font_size.to_pixels(window.rem_size());
|
||||
|
||||
self.base
|
||||
.text_color(text_color)
|
||||
.size(text_size)
|
||||
.when_some(self.size, |this, size| match size {
|
||||
Size::Size(px) => this.size(px),
|
||||
Size::XSmall => this.size_3(),
|
||||
|
|
@ -301,12 +303,14 @@ impl From<Icon> for AnyElement {
|
|||
}
|
||||
|
||||
impl Render for Icon {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
|
||||
let text_size = window.text_style().font_size.to_pixels(window.rem_size());
|
||||
|
||||
svg()
|
||||
.flex_none()
|
||||
.text_color(text_color)
|
||||
.size(text_size)
|
||||
.when_some(self.size, |this, size| match size {
|
||||
Size::Size(px) => this.size(px),
|
||||
Size::XSmall => this.size_3(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue