Update call controls & Add tinted buttons (#3886)

This PR introduces Tinted button styles and the `selected_style` field
on buttons to allow replicating the previous design of titlebar call
controls. It also updates the styles of the titlebar controls.

### Creating a tinted button:

```
Button::new("accept-cta", "Accept")
    .style(ButtonStyle::Tinted(TintColor::Accent))
    .on_click(...)
```

Ths button will always be tinted blue.

### Creating a button that becomes tinted when selected::

```rust
IconButton::new("screen-share", Icon::Screen)
    .style(ButtonStyle::Subtle)
    .selected(is_screen_sharing)
    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
    .on_click(...),
```

This button will be flat/subtle by default, but be tinted blue when it
is `selected`.

Note: There appears to be some issue where `is_deafened` isn't
activating correctly, making the speaker icon not toggle when selected.

Release Notes:

- Restore call control styles to a similar look to Zed 1.
This commit is contained in:
Nate Butler 2024-01-05 11:28:18 -05:00 committed by GitHub
commit a205b2dbf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 39 deletions

View file

@ -14,7 +14,7 @@ use std::sync::Arc;
use theme::{ActiveTheme, PlayerColors}; use theme::{ActiveTheme, PlayerColors};
use ui::{ use ui::{
h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
IconButton, IconElement, Tooltip, IconButton, IconElement, TintColor, Tooltip,
}; };
use util::ResultExt; use util::ResultExt;
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
@ -183,6 +183,8 @@ impl Render for CollabTitlebarItem {
if is_shared { "Unshare" } else { "Share" }, if is_shared { "Unshare" } else { "Share" },
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.selected(is_shared)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener( .on_click(cx.listener(
move |this, _, cx| { move |this, _, cx| {
@ -215,6 +217,7 @@ impl Render for CollabTitlebarItem {
}, },
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.selected(is_muted) .selected(is_muted)
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)), .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
@ -229,6 +232,7 @@ impl Render for CollabTitlebarItem {
}, },
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.selected(is_deafened) .selected(is_deafened)
.tooltip(move |cx| { .tooltip(move |cx| {
@ -239,6 +243,7 @@ impl Render for CollabTitlebarItem {
.child( .child(
IconButton::new("screen-share", ui::Icon::Screen) IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.selected(is_screen_sharing) .selected(is_screen_sharing)
.on_click(move |_, cx| { .on_click(move |_, cx| {

View file

@ -92,6 +92,13 @@ impl Selectable for Button {
} }
} }
impl SelectableButton for Button {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.selected_style(style);
self
}
}
impl Disableable for Button { impl Disableable for Button {
fn disabled(mut self, disabled: bool) -> Self { fn disabled(mut self, disabled: bool) -> Self {
self.base = self.base.disabled(disabled); self.base = self.base.disabled(disabled);

View file

@ -12,6 +12,7 @@ pub(super) struct ButtonIcon {
disabled: bool, disabled: bool,
selected: bool, selected: bool,
selected_icon: Option<Icon>, selected_icon: Option<Icon>,
selected_style: Option<ButtonStyle>,
} }
impl ButtonIcon { impl ButtonIcon {
@ -23,6 +24,7 @@ impl ButtonIcon {
disabled: false, disabled: false,
selected: false, selected: false,
selected_icon: None, selected_icon: None,
selected_style: None,
} }
} }
@ -62,6 +64,13 @@ impl Selectable for ButtonIcon {
} }
} }
impl SelectableButton for ButtonIcon {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.selected_style = Some(style);
self
}
}
impl RenderOnce for ButtonIcon { impl RenderOnce for ButtonIcon {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let icon = self let icon = self
@ -71,6 +80,8 @@ impl RenderOnce for ButtonIcon {
let icon_color = if self.disabled { let icon_color = if self.disabled {
Color::Disabled Color::Disabled
} else if self.selected_style.is_some() && self.selected {
self.selected_style.unwrap().into()
} else if self.selected { } else if self.selected {
Color::Selected Color::Selected
} else { } else {

View file

@ -4,6 +4,10 @@ use smallvec::SmallVec;
use crate::prelude::*; use crate::prelude::*;
pub trait SelectableButton: Selectable {
fn selected_style(self, style: ButtonStyle) -> Self;
}
pub trait ButtonCommon: Clickable + Disableable { pub trait ButtonCommon: Clickable + Disableable {
/// A unique element ID to identify the button. /// A unique element ID to identify the button.
fn id(&self) -> &ElementId; fn id(&self) -> &ElementId;
@ -36,17 +40,68 @@ pub enum IconPosition {
End, End,
} }
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum TintColor {
#[default]
Accent,
Negative,
Warning,
}
impl TintColor {
fn button_like_style(self, cx: &mut WindowContext) -> ButtonLikeStyles {
match self {
TintColor::Accent => ButtonLikeStyles {
background: cx.theme().status().info_background,
border_color: cx.theme().status().info_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
TintColor::Negative => ButtonLikeStyles {
background: cx.theme().status().error_background,
border_color: cx.theme().status().error_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
TintColor::Warning => ButtonLikeStyles {
background: cx.theme().status().warning_background,
border_color: cx.theme().status().warning_border,
label_color: cx.theme().colors().text,
icon_color: cx.theme().colors().text,
},
}
}
}
impl From<TintColor> for Color {
fn from(tint: TintColor) -> Self {
match tint {
TintColor::Accent => Color::Accent,
TintColor::Negative => Color::Error,
TintColor::Warning => Color::Warning,
}
}
}
// Used to go from ButtonStyle -> Color through tint colors.
impl From<ButtonStyle> for Color {
fn from(style: ButtonStyle) -> Self {
match style {
ButtonStyle::Tinted(tint) => tint.into(),
_ => Color::Default,
}
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum ButtonStyle { pub enum ButtonStyle {
/// A filled button with a solid background color. Provides emphasis versus /// A filled button with a solid background color. Provides emphasis versus
/// the more common subtle button. /// the more common subtle button.
Filled, Filled,
/// 🚧 Under construction 🚧
///
/// Used to emphasize a button in some way, like a selected state, or a semantic /// Used to emphasize a button in some way, like a selected state, or a semantic
/// coloring like an error or success button. /// coloring like an error or success button.
Tinted, Tinted(TintColor),
/// The default button style, used for most buttons. Has a transparent background, /// The default button style, used for most buttons. Has a transparent background,
/// but has a background color to indicate states like hover and active. /// but has a background color to indicate states like hover and active.
@ -86,12 +141,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx), label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx), icon_color: Color::Default.color(cx),
}, },
ButtonStyle::Tinted => ButtonLikeStyles { ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles { ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background, background: cx.theme().colors().ghost_element_background,
border_color: transparent_black(), border_color: transparent_black(),
@ -115,12 +165,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx), label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx), icon_color: Color::Default.color(cx),
}, },
ButtonStyle::Tinted => ButtonLikeStyles { ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles { ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_hover, background: cx.theme().colors().ghost_element_hover,
border_color: transparent_black(), border_color: transparent_black(),
@ -146,12 +191,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx), label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx), icon_color: Color::Default.color(cx),
}, },
ButtonStyle::Tinted => ButtonLikeStyles { ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles { ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_active, background: cx.theme().colors().ghost_element_active,
border_color: transparent_black(), border_color: transparent_black(),
@ -178,12 +218,7 @@ impl ButtonStyle {
label_color: Color::Default.color(cx), label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx), icon_color: Color::Default.color(cx),
}, },
ButtonStyle::Tinted => ButtonLikeStyles { ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles { ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background, background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border_focused, border_color: cx.theme().colors().border_focused,
@ -208,12 +243,7 @@ impl ButtonStyle {
label_color: Color::Disabled.color(cx), label_color: Color::Disabled.color(cx),
icon_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx),
}, },
ButtonStyle::Tinted => ButtonLikeStyles { ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
background: gpui::red(),
border_color: gpui::red(),
label_color: gpui::red(),
icon_color: gpui::red(),
},
ButtonStyle::Subtle => ButtonLikeStyles { ButtonStyle::Subtle => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_disabled, background: cx.theme().colors().ghost_element_disabled,
border_color: cx.theme().colors().border_disabled, border_color: cx.theme().colors().border_disabled,
@ -264,6 +294,7 @@ pub struct ButtonLike {
pub(super) style: ButtonStyle, pub(super) style: ButtonStyle,
pub(super) disabled: bool, pub(super) disabled: bool,
pub(super) selected: bool, pub(super) selected: bool,
pub(super) selected_style: Option<ButtonStyle>,
pub(super) width: Option<DefiniteLength>, pub(super) width: Option<DefiniteLength>,
size: ButtonSize, size: ButtonSize,
rounding: Option<ButtonLikeRounding>, rounding: Option<ButtonLikeRounding>,
@ -280,6 +311,7 @@ impl ButtonLike {
style: ButtonStyle::default(), style: ButtonStyle::default(),
disabled: false, disabled: false,
selected: false, selected: false,
selected_style: None,
width: None, width: None,
size: ButtonSize::Default, size: ButtonSize::Default,
rounding: Some(ButtonLikeRounding::All), rounding: Some(ButtonLikeRounding::All),
@ -309,6 +341,13 @@ impl Selectable for ButtonLike {
} }
} }
impl SelectableButton for ButtonLike {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.selected_style = Some(style);
self
}
}
impl Clickable for ButtonLike { impl Clickable for ButtonLike {
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler)); self.on_click = Some(Box::new(handler));
@ -364,6 +403,11 @@ impl ParentElement for ButtonLike {
impl RenderOnce for ButtonLike { impl RenderOnce for ButtonLike {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let style = self
.selected_style
.filter(|_| self.selected)
.unwrap_or(self.style);
self.base self.base
.h_flex() .h_flex()
.id(self.id.clone()) .id(self.id.clone())
@ -382,12 +426,12 @@ impl RenderOnce for ButtonLike {
ButtonSize::Default | ButtonSize::Compact => this.px_1(), ButtonSize::Default | ButtonSize::Compact => this.px_1(),
ButtonSize::None => this, ButtonSize::None => this,
}) })
.bg(self.style.enabled(cx).background) .bg(style.enabled(cx).background)
.when(self.disabled, |this| this.cursor_not_allowed()) .when(self.disabled, |this| this.cursor_not_allowed())
.when(!self.disabled, |this| { .when(!self.disabled, |this| {
this.cursor_pointer() this.cursor_pointer()
.hover(|hover| hover.bg(self.style.hovered(cx).background)) .hover(|hover| hover.bg(style.hovered(cx).background))
.active(|active| active.bg(self.style.active(cx).background)) .active(|active| active.bg(style.active(cx).background))
}) })
.when_some( .when_some(
self.on_click.filter(|_| !self.disabled), self.on_click.filter(|_| !self.disabled),

View file

@ -1,6 +1,6 @@
use gpui::{AnyView, DefiniteLength}; use gpui::{AnyView, DefiniteLength};
use crate::prelude::*; use crate::{prelude::*, SelectableButton};
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
use super::button_icon::ButtonIcon; use super::button_icon::ButtonIcon;
@ -55,6 +55,13 @@ impl Selectable for IconButton {
} }
} }
impl SelectableButton for IconButton {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base = self.base.selected_style(style);
self
}
}
impl Clickable for IconButton { impl Clickable for IconButton {
fn on_click( fn on_click(
mut self, mut self,
@ -109,12 +116,14 @@ impl RenderOnce for IconButton {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement { fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let is_disabled = self.base.disabled; let is_disabled = self.base.disabled;
let is_selected = self.base.selected; let is_selected = self.base.selected;
let selected_style = self.base.selected_style;
self.base.child( self.base.child(
ButtonIcon::new(self.icon) ButtonIcon::new(self.icon)
.disabled(is_disabled) .disabled(is_disabled)
.selected(is_selected) .selected(is_selected)
.selected_icon(self.selected_icon) .selected_icon(self.selected_icon)
.when_some(selected_style, |this, style| this.selected_style(style))
.size(self.icon_size) .size(self.icon_size)
.color(self.icon_color), .color(self.icon_color),
) )

View file

@ -63,6 +63,13 @@ impl Selectable for ToggleButton {
} }
} }
impl SelectableButton for ToggleButton {
fn selected_style(mut self, style: ButtonStyle) -> Self {
self.base.selected_style = Some(style);
self
}
}
impl Disableable for ToggleButton { impl Disableable for ToggleButton {
fn disabled(mut self, disabled: bool) -> Self { fn disabled(mut self, disabled: bool) -> Self {
self.base = self.base.disabled(disabled); self.base = self.base.disabled(disabled);

View file

@ -12,7 +12,7 @@ pub use crate::selectable::*;
pub use crate::styles::{vh, vw}; pub use crate::styles::{vh, vw};
pub use crate::visible_on_hover::*; pub use crate::visible_on_hover::*;
pub use crate::{h_stack, v_stack}; pub use crate::{h_stack, v_stack};
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{ButtonCommon, Color, StyledExt};
pub use crate::{Icon, IconElement, IconPosition, IconSize}; pub use crate::{Icon, IconElement, IconPosition, IconSize};
pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};