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
|
- Breadcrumb
|
||||||
- Badge
|
- Badge
|
||||||
- TextView (Markdown, Simple HTML) to native rendering, syntax highlighting.
|
- TextView (Markdown, Simple HTML) to native rendering, syntax highlighting.
|
||||||
|
- Toggle, ToggleGroup
|
||||||
|
|
||||||
## Showcase
|
## 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 tabs_story;
|
||||||
mod text_story;
|
mod text_story;
|
||||||
mod title_bar;
|
mod title_bar;
|
||||||
|
mod toggle_story;
|
||||||
mod tooltip_story;
|
mod tooltip_story;
|
||||||
mod webview_story;
|
mod webview_story;
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ pub use calendar_story::CalendarStory;
|
||||||
pub use dropdown_story::DropdownStory;
|
pub use dropdown_story::DropdownStory;
|
||||||
pub use form_story::FormStory;
|
pub use form_story::FormStory;
|
||||||
pub use tabs_story::TabsStory;
|
pub use tabs_story::TabsStory;
|
||||||
|
pub use toggle_story::ToggleStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
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 {
|
fn selected(&self, cx: &mut App) -> ButtonVariantStyle {
|
||||||
let bg = match self {
|
let bg = match self {
|
||||||
ButtonVariant::Primary => cx.theme().primary_active,
|
ButtonVariant::Primary => cx.theme().primary_active,
|
||||||
ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_active,
|
ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => {
|
||||||
ButtonVariant::Ghost => {
|
cx.theme().secondary_active
|
||||||
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::Danger => cx.theme().danger_active,
|
ButtonVariant::Danger => cx.theme().danger_active,
|
||||||
ButtonVariant::Link => cx.theme().transparent,
|
ButtonVariant::Link => cx.theme().transparent,
|
||||||
ButtonVariant::Text => cx.theme().transparent,
|
ButtonVariant::Text => cx.theme().transparent,
|
||||||
ButtonVariant::Custom(colors) => colors.active,
|
ButtonVariant::Custom(colors) => colors.active,
|
||||||
};
|
}
|
||||||
|
.opacity(0.5);
|
||||||
|
|
||||||
let border = self.border_color(cx);
|
let border = self.border_color(cx);
|
||||||
let fg = match self {
|
let fg = match self {
|
||||||
ButtonVariant::Link => cx.theme().link_active,
|
ButtonVariant::Link => cx.theme().link_active,
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ pub struct ButtonGroup {
|
||||||
pub base: Div,
|
pub base: Div,
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
children: Vec<Button>,
|
children: Vec<Button>,
|
||||||
multiple: bool,
|
pub(super) multiple: bool,
|
||||||
disabled: bool,
|
pub(super) disabled: bool,
|
||||||
|
|
||||||
// The button props
|
// The button props
|
||||||
compact: Option<bool>,
|
pub(super) compact: Option<bool>,
|
||||||
variant: Option<ButtonVariant>,
|
pub(super) variant: Option<ButtonVariant>,
|
||||||
size: Option<Size>,
|
pub(super) size: Option<Size>,
|
||||||
|
|
||||||
on_click: Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Box<dyn Fn(&Vec<usize>, &mut Window, &mut App) + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +55,12 @@ impl ButtonGroup {
|
||||||
self
|
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.
|
/// With the multiple selection mode.
|
||||||
pub fn multiple(mut self, multiple: bool) -> Self {
|
pub fn multiple(mut self, multiple: bool) -> Self {
|
||||||
self.multiple = multiple;
|
self.multiple = multiple;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
mod button;
|
mod button;
|
||||||
mod button_group;
|
mod button_group;
|
||||||
mod dropdown_button;
|
mod dropdown_button;
|
||||||
|
mod toggle;
|
||||||
|
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
pub use button_group::*;
|
pub use button_group::*;
|
||||||
pub use dropdown_button::*;
|
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 {
|
impl RenderOnce for Icon {
|
||||||
fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
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_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
|
self.base
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
|
.size(text_size)
|
||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
Size::XSmall => this.size_3(),
|
Size::XSmall => this.size_3(),
|
||||||
|
|
@ -301,12 +303,14 @@ impl From<Icon> for AnyElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Icon {
|
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_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
|
||||||
|
let text_size = window.text_style().font_size.to_pixels(window.rem_size());
|
||||||
|
|
||||||
svg()
|
svg()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.text_color(text_color)
|
.text_color(text_color)
|
||||||
|
.size(text_size)
|
||||||
.when_some(self.size, |this, size| match size {
|
.when_some(self.size, |this, size| match size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
Size::XSmall => this.size_3(),
|
Size::XSmall => this.size_3(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue