https://github.com/user-attachments/assets/b16e32e6-46c6-41dc-ab68-1824d288c8c2 This PR adds the first part of our planned extended notification system: StatusToasts. It also makes various updates to ComponentPreview and adds a `Styled` extension in `ui::style::animation` to make it easier to animate styled elements. _**Note**: We will be very, very selective with what elements are allowed to be animated in Zed. Assume PRs adding animation to elements will all need to be manually signed off on by a designer._ ## Status Toast  These are designed to be used for notifying about things that don't require an action to be taken or don't need to be triaged. They are designed to be ignorable, and dismiss themselves automatically after a set time. They can optionally include a single action. Example: When the user enables Vim Mode, that action might let them undo enabling it.  Status Toasts should _not_ be used when an action is required, or for any binary choice. If the user must provide some input, this isn't the right component! ### Out of scope - Toasts should fade over a short time (like AnimationDuration::Fast or Instant) when dismissed - We should visually show when the toast will dismiss. We'll need to pipe the `duration_remaining` from the toast layer -> ActiveToast to do this. - Dismiss any active toast if another notification kind is created, like a Notification or Alert. Release Notes: - N/A --------- Co-authored-by: Cole Miller <m@cole-miller.net>
272 lines
9.8 KiB
Rust
272 lines
9.8 KiB
Rust
use gpui::{AnyView, ClickEvent};
|
||
|
||
use crate::{prelude::*, ButtonLike, ButtonLikeRounding, ElevationIndex};
|
||
|
||
/// The position of a [`ToggleButton`] within a group of buttons.
|
||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||
pub enum ToggleButtonPosition {
|
||
/// The toggle button is first in the group.
|
||
First,
|
||
|
||
/// The toggle button is in the middle of the group (i.e., it is not the first or last toggle button).
|
||
Middle,
|
||
|
||
/// The toggle button is last in the group.
|
||
Last,
|
||
}
|
||
|
||
#[derive(IntoElement, IntoComponent)]
|
||
#[component(scope = "Input")]
|
||
pub struct ToggleButton {
|
||
base: ButtonLike,
|
||
position_in_group: Option<ToggleButtonPosition>,
|
||
label: SharedString,
|
||
label_color: Option<Color>,
|
||
}
|
||
|
||
impl ToggleButton {
|
||
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||
Self {
|
||
base: ButtonLike::new(id),
|
||
position_in_group: None,
|
||
label: label.into(),
|
||
label_color: None,
|
||
}
|
||
}
|
||
|
||
pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
|
||
self.label_color = label_color.into();
|
||
self
|
||
}
|
||
|
||
pub fn position_in_group(mut self, position: ToggleButtonPosition) -> Self {
|
||
self.position_in_group = Some(position);
|
||
self
|
||
}
|
||
|
||
pub fn first(self) -> Self {
|
||
self.position_in_group(ToggleButtonPosition::First)
|
||
}
|
||
|
||
pub fn middle(self) -> Self {
|
||
self.position_in_group(ToggleButtonPosition::Middle)
|
||
}
|
||
|
||
pub fn last(self) -> Self {
|
||
self.position_in_group(ToggleButtonPosition::Last)
|
||
}
|
||
}
|
||
|
||
impl Toggleable for ToggleButton {
|
||
fn toggle_state(mut self, selected: bool) -> Self {
|
||
self.base = self.base.toggle_state(selected);
|
||
self
|
||
}
|
||
}
|
||
|
||
impl SelectableButton for ToggleButton {
|
||
fn selected_style(mut self, style: ButtonStyle) -> Self {
|
||
self.base.selected_style = Some(style);
|
||
self
|
||
}
|
||
}
|
||
|
||
impl Disableable for ToggleButton {
|
||
fn disabled(mut self, disabled: bool) -> Self {
|
||
self.base = self.base.disabled(disabled);
|
||
self
|
||
}
|
||
}
|
||
|
||
impl Clickable for ToggleButton {
|
||
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
|
||
self.base = self.base.on_click(handler);
|
||
self
|
||
}
|
||
|
||
fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
|
||
self.base = self.base.cursor_style(cursor_style);
|
||
self
|
||
}
|
||
}
|
||
|
||
impl ButtonCommon for ToggleButton {
|
||
fn id(&self) -> &ElementId {
|
||
self.base.id()
|
||
}
|
||
|
||
fn style(mut self, style: ButtonStyle) -> Self {
|
||
self.base = self.base.style(style);
|
||
self
|
||
}
|
||
|
||
fn size(mut self, size: ButtonSize) -> Self {
|
||
self.base = self.base.size(size);
|
||
self
|
||
}
|
||
|
||
fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
|
||
self.base = self.base.tooltip(tooltip);
|
||
self
|
||
}
|
||
|
||
fn layer(mut self, elevation: ElevationIndex) -> Self {
|
||
self.base = self.base.layer(elevation);
|
||
self
|
||
}
|
||
}
|
||
|
||
impl RenderOnce for ToggleButton {
|
||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||
let is_disabled = self.base.disabled;
|
||
let is_selected = self.base.selected;
|
||
|
||
let label_color = if is_disabled {
|
||
Color::Disabled
|
||
} else if is_selected {
|
||
Color::Selected
|
||
} else {
|
||
self.label_color.unwrap_or_default()
|
||
};
|
||
|
||
self.base
|
||
.when_some(self.position_in_group, |this, position| match position {
|
||
ToggleButtonPosition::First => this.rounding(ButtonLikeRounding::Left),
|
||
ToggleButtonPosition::Middle => this.rounding(None),
|
||
ToggleButtonPosition::Last => this.rounding(ButtonLikeRounding::Right),
|
||
})
|
||
.child(
|
||
Label::new(self.label)
|
||
.color(label_color)
|
||
.line_height_style(LineHeightStyle::UiLabel),
|
||
)
|
||
}
|
||
}
|
||
|
||
impl ComponentPreview for ToggleButton {
|
||
fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
|
||
v_flex()
|
||
.gap_6()
|
||
.children(vec![
|
||
example_group_with_title(
|
||
"Button Styles",
|
||
vec![
|
||
single_example(
|
||
"Off",
|
||
ToggleButton::new("off", "Off")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"On",
|
||
ToggleButton::new("on", "On")
|
||
.layer(ElevationIndex::Background)
|
||
.toggle_state(true)
|
||
.style(ButtonStyle::Filled)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"Off – Disabled",
|
||
ToggleButton::new("disabled_off", "Disabled Off")
|
||
.layer(ElevationIndex::Background)
|
||
.disabled(true)
|
||
.style(ButtonStyle::Filled)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"On – Disabled",
|
||
ToggleButton::new("disabled_on", "Disabled On")
|
||
.layer(ElevationIndex::Background)
|
||
.disabled(true)
|
||
.toggle_state(true)
|
||
.style(ButtonStyle::Filled)
|
||
.into_any_element(),
|
||
),
|
||
],
|
||
),
|
||
example_group_with_title(
|
||
"Button Group",
|
||
vec![
|
||
single_example(
|
||
"Three Buttons",
|
||
h_flex()
|
||
.child(
|
||
ToggleButton::new("three_btn_first", "First")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.first()
|
||
.into_any_element(),
|
||
)
|
||
.child(
|
||
ToggleButton::new("three_btn_middle", "Middle")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.middle()
|
||
.toggle_state(true)
|
||
.into_any_element(),
|
||
)
|
||
.child(
|
||
ToggleButton::new("three_btn_last", "Last")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.last()
|
||
.into_any_element(),
|
||
)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"Two Buttons",
|
||
h_flex()
|
||
.child(
|
||
ToggleButton::new("two_btn_first", "First")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.first()
|
||
.into_any_element(),
|
||
)
|
||
.child(
|
||
ToggleButton::new("two_btn_last", "Last")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.last()
|
||
.into_any_element(),
|
||
)
|
||
.into_any_element(),
|
||
),
|
||
],
|
||
),
|
||
example_group_with_title(
|
||
"Alternate Sizes",
|
||
vec![
|
||
single_example(
|
||
"None",
|
||
ToggleButton::new("none", "None")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.size(ButtonSize::None)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"Compact",
|
||
ToggleButton::new("compact", "Compact")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.size(ButtonSize::Compact)
|
||
.into_any_element(),
|
||
),
|
||
single_example(
|
||
"Large",
|
||
ToggleButton::new("large", "Large")
|
||
.layer(ElevationIndex::Background)
|
||
.style(ButtonStyle::Filled)
|
||
.size(ButtonSize::Large)
|
||
.into_any_element(),
|
||
),
|
||
],
|
||
),
|
||
])
|
||
.into_any_element()
|
||
}
|
||
}
|