Simplify toggle, some ui2 reorganization (#3234)

[[PR Description]]

A few mix organizational things in UI, as well as some toggle changes.

Simplify toggle:

```rust
/// Whether the entry is toggleable, and if so, whether it is currently toggled.
///
/// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases.
///
/// You can check if an element is toggleable with `.is_toggleable()`
///
/// Possible values:
/// - `Toggle::NotToggleable` - The entry is not toggleable
/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled
/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Toggle {
    NotToggleable,
    Toggled(bool),
}
```

Adds helper functions to easily get the toggle and toggleable states:

```rust
impl Toggle {
    /// Returns true if the entry is toggled (or is not toggleable.)
    ///
    /// As element that isn't toggleable is always "expanded" or "enabled"
    /// returning true in that case makes sense.
    pub fn is_toggled(&self) -> bool {
        match self {
            Self::Toggled(false) => false,
            _ => true,
        }
    }

    pub fn is_toggleable(&self) -> bool {
        match self {
            Self::Toggled(_) => true,
            _ => false,
        }
    }
}
```

Pulls `disclosure_control` out of components and creates a common def:

```rust
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
    match (toggle.is_toggleable(), toggle.is_toggled()) {
        (false, _) => div(),
        (_, true) => div().child(
            IconElement::new(Icon::ChevronDown)
                .color(IconColor::Muted)
                .size(IconSize::Small),
        ),
        (_, false) => div().child(
            IconElement::new(Icon::ChevronRight)
                .color(IconColor::Muted)
                .size(IconSize::Small),
        ),
    }
}
```

disclosure_control will likely get pulled into it's own component in the
future instead of being in toggle.

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2023-11-04 12:09:41 -04:00 committed by GitHub
commit b243de407e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 224 additions and 499 deletions

View File

@ -16,9 +16,11 @@ mod palette;
mod panel; mod panel;
mod player; mod player;
mod player_stack; mod player_stack;
mod slot;
mod stack; mod stack;
mod tab; mod tab;
mod toast; mod toast;
mod toggle;
mod tool_divider; mod tool_divider;
pub use avatar::*; pub use avatar::*;
@ -39,7 +41,9 @@ pub use palette::*;
pub use panel::*; pub use panel::*;
pub use player::*; pub use player::*;
pub use player_stack::*; pub use player_stack::*;
pub use slot::*;
pub use stack::*; pub use stack::*;
pub use tab::*; pub use tab::*;
pub use toast::*; pub use toast::*;
pub use toggle::*;
pub use tool_divider::*; pub use tool_divider::*;

View File

@ -2,8 +2,27 @@ use std::sync::Arc;
use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext}; use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext};
use crate::prelude::*;
use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle}; use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle};
use crate::{prelude::*, IconButton};
/// Provides the flexibility to use either a standard
/// button or an icon button in a given context.
pub enum ButtonOrIconButton<V: 'static> {
Button(Button<V>),
IconButton(IconButton<V>),
}
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> Self {
Self::IconButton(value)
}
}
#[derive(Default, PartialEq, Clone, Copy)] #[derive(Default, PartialEq, Clone, Copy)]
pub enum IconPosition { pub enum IconPosition {

View File

@ -8,7 +8,7 @@ pub enum ContextMenuItem {
} }
impl ContextMenuItem { impl ContextMenuItem {
fn to_list_item<V: 'static>(self) -> ListItem<V> { fn to_list_item<V: 'static>(self) -> ListItem {
match self { match self {
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
ContextMenuItem::Entry(label) => { ContextMenuItem::Entry(label) => {
@ -49,15 +49,12 @@ impl ContextMenu {
.bg(cx.theme().colors().elevated_surface) .bg(cx.theme().colors().elevated_surface)
.border() .border()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child( .child(List::new(
List::new( self.items
self.items .into_iter()
.into_iter() .map(ContextMenuItem::to_list_item::<V>)
.map(ContextMenuItem::to_list_item) .collect(),
.collect(), ))
)
.toggle(ToggleState::Toggled),
)
} }
} }

View File

@ -1,11 +1,11 @@
use gpui2::{div, px, relative, Div}; use gpui2::div;
use crate::settings::user_settings; use crate::settings::user_settings;
use crate::{ use crate::{
h_stack, v_stack, Avatar, ClickHandler, Icon, IconColor, IconElement, IconSize, Label, disclosure_control, h_stack, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label,
LabelColor, LabelColor, Toggle,
}; };
use crate::{prelude::*, Button}; use crate::{prelude::*, GraphicSlot};
#[derive(Clone, Copy, Default, Debug, PartialEq)] #[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum ListItemVariant { pub enum ListItemVariant {
@ -29,7 +29,7 @@ pub struct ListHeader {
left_icon: Option<Icon>, left_icon: Option<Icon>,
meta: Option<ListHeaderMeta>, meta: Option<ListHeaderMeta>,
variant: ListItemVariant, variant: ListItemVariant,
toggleable: Toggleable, toggle: Toggle,
} }
impl ListHeader { impl ListHeader {
@ -39,17 +39,12 @@ impl ListHeader {
left_icon: None, left_icon: None,
meta: None, meta: None,
variant: ListItemVariant::default(), variant: ListItemVariant::default(),
toggleable: Toggleable::NotToggleable, toggle: Toggle::NotToggleable,
} }
} }
pub fn toggle(mut self, toggle: ToggleState) -> Self { pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggleable = toggle.into(); self.toggle = toggle;
self
}
pub fn toggleable(mut self, toggleable: Toggleable) -> Self {
self.toggleable = toggleable;
self self
} }
@ -63,30 +58,8 @@ impl ListHeader {
self self
} }
fn disclosure_control<V: 'static>(&self) -> Div<V> {
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
let is_toggled = Toggleable::is_toggled(&self.toggleable);
match (is_toggleable, is_toggled) {
(false, _) => div(),
(_, true) => div().child(
IconElement::new(Icon::ChevronDown)
.color(IconColor::Muted)
.size(IconSize::Small),
),
(_, false) => div().child(
IconElement::new(Icon::ChevronRight)
.color(IconColor::Muted)
.size(IconSize::Small),
),
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let is_toggleable = self.toggleable != Toggleable::NotToggleable; let disclosure_control = disclosure_control(self.toggle);
let is_toggled = self.toggleable.is_toggled();
let disclosure_control = self.disclosure_control();
let meta = match self.meta { let meta = match self.meta {
Some(ListHeaderMeta::Tools(icons)) => div().child( Some(ListHeaderMeta::Tools(icons)) => div().child(
@ -193,12 +166,6 @@ impl ListSubHeader {
} }
} }
#[derive(Clone)]
pub enum LeftContent {
Icon(Icon),
Avatar(SharedString),
}
#[derive(Default, PartialEq, Copy, Clone)] #[derive(Default, PartialEq, Copy, Clone)]
pub enum ListEntrySize { pub enum ListEntrySize {
#[default] #[default]
@ -207,44 +174,36 @@ pub enum ListEntrySize {
} }
#[derive(Component)] #[derive(Component)]
pub enum ListItem<V: 'static> { pub enum ListItem {
Entry(ListEntry), Entry(ListEntry),
Details(ListDetailsEntry<V>),
Separator(ListSeparator), Separator(ListSeparator),
Header(ListSubHeader), Header(ListSubHeader),
} }
impl<V: 'static> From<ListEntry> for ListItem<V> { impl From<ListEntry> for ListItem {
fn from(entry: ListEntry) -> Self { fn from(entry: ListEntry) -> Self {
Self::Entry(entry) Self::Entry(entry)
} }
} }
impl<V: 'static> From<ListDetailsEntry<V>> for ListItem<V> { impl From<ListSeparator> for ListItem {
fn from(entry: ListDetailsEntry<V>) -> Self {
Self::Details(entry)
}
}
impl<V: 'static> From<ListSeparator> for ListItem<V> {
fn from(entry: ListSeparator) -> Self { fn from(entry: ListSeparator) -> Self {
Self::Separator(entry) Self::Separator(entry)
} }
} }
impl<V: 'static> From<ListSubHeader> for ListItem<V> { impl From<ListSubHeader> for ListItem {
fn from(entry: ListSubHeader) -> Self { fn from(entry: ListSubHeader) -> Self {
Self::Header(entry) Self::Header(entry)
} }
} }
impl<V: 'static> ListItem<V> { impl ListItem {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
match self { match self {
ListItem::Entry(entry) => div().child(entry.render(view, cx)), ListItem::Entry(entry) => div().child(entry.render(view, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)), ListItem::Separator(separator) => div().child(separator.render(view, cx)),
ListItem::Header(header) => div().child(header.render(view, cx)), ListItem::Header(header) => div().child(header.render(view, cx)),
ListItem::Details(details) => div().child(details.render(view, cx)),
} }
} }
@ -263,31 +222,29 @@ impl<V: 'static> ListItem<V> {
#[derive(Component)] #[derive(Component)]
pub struct ListEntry { pub struct ListEntry {
disclosure_control_style: DisclosureControlVisibility, disabled: bool,
// TODO: Reintroduce this
// disclosure_control_style: DisclosureControlVisibility,
indent_level: u32, indent_level: u32,
label: Label, label: Label,
left_content: Option<LeftContent>, left_slot: Option<GraphicSlot>,
variant: ListItemVariant,
size: ListEntrySize,
state: InteractionState,
toggle: Option<ToggleState>,
overflow: OverflowStyle, overflow: OverflowStyle,
size: ListEntrySize,
toggle: Toggle,
variant: ListItemVariant,
} }
impl ListEntry { impl ListEntry {
pub fn new(label: Label) -> Self { pub fn new(label: Label) -> Self {
Self { Self {
disclosure_control_style: DisclosureControlVisibility::default(), disabled: false,
indent_level: 0, indent_level: 0,
label, label,
variant: ListItemVariant::default(), left_slot: None,
left_content: None,
size: ListEntrySize::default(),
state: InteractionState::default(),
// TODO: Should use Toggleable::NotToggleable
// or remove Toggleable::NotToggleable from the system
toggle: None,
overflow: OverflowStyle::Hidden, overflow: OverflowStyle::Hidden,
size: ListEntrySize::default(),
toggle: Toggle::NotToggleable,
variant: ListItemVariant::default(),
} }
} }
@ -301,28 +258,23 @@ impl ListEntry {
self self
} }
pub fn toggle(mut self, toggle: ToggleState) -> Self { pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = Some(toggle); self.toggle = toggle;
self self
} }
pub fn left_content(mut self, left_content: LeftContent) -> Self { pub fn left_content(mut self, left_content: GraphicSlot) -> Self {
self.left_content = Some(left_content); self.left_slot = Some(left_content);
self self
} }
pub fn left_icon(mut self, left_icon: Icon) -> Self { pub fn left_icon(mut self, left_icon: Icon) -> Self {
self.left_content = Some(LeftContent::Icon(left_icon)); self.left_slot = Some(GraphicSlot::Icon(left_icon));
self self
} }
pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self { pub fn left_avatar(mut self, left_avatar: impl Into<SharedString>) -> Self {
self.left_content = Some(LeftContent::Avatar(left_avatar.into())); self.left_slot = Some(GraphicSlot::Avatar(left_avatar.into()));
self
}
pub fn state(mut self, state: InteractionState) -> Self {
self.state = state;
self self
} }
@ -331,63 +283,19 @@ impl ListEntry {
self self
} }
pub fn disclosure_control_style( fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
mut self,
disclosure_control_style: DisclosureControlVisibility,
) -> Self {
self.disclosure_control_style = disclosure_control_style;
self
}
fn label_color(&self) -> LabelColor {
match self.state {
InteractionState::Disabled => LabelColor::Disabled,
_ => Default::default(),
}
}
fn icon_color(&self) -> IconColor {
match self.state {
InteractionState::Disabled => IconColor::Disabled,
_ => Default::default(),
}
}
fn disclosure_control<V: 'static>(
&mut self,
cx: &mut ViewContext<V>,
) -> Option<impl Component<V>> {
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
IconElement::new(Icon::ChevronDown)
} else {
IconElement::new(Icon::ChevronRight)
}
.color(IconColor::Muted)
.size(IconSize::Small);
match (self.toggle, self.disclosure_control_style) {
(Some(_), DisclosureControlVisibility::OnHover) => {
Some(div().absolute().neg_left_5().child(disclosure_control_icon))
}
(Some(_), DisclosureControlVisibility::Always) => {
Some(div().child(disclosure_control_icon))
}
(None, _) => None,
}
}
fn render<V: 'static>(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let settings = user_settings(cx); let settings = user_settings(cx);
let left_content = match self.left_content.clone() { let left_content = match self.left_slot.clone() {
Some(LeftContent::Icon(i)) => Some( Some(GraphicSlot::Icon(i)) => Some(
h_stack().child( h_stack().child(
IconElement::new(i) IconElement::new(i)
.size(IconSize::Small) .size(IconSize::Small)
.color(IconColor::Muted), .color(IconColor::Muted),
), ),
), ),
Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))), Some(GraphicSlot::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
Some(GraphicSlot::PublicActor(src)) => Some(h_stack().child(Avatar::new(src))),
None => None, None => None,
}; };
@ -400,10 +308,7 @@ impl ListEntry {
.relative() .relative()
.group("") .group("")
.bg(cx.theme().colors().surface) .bg(cx.theme().colors().surface)
.when(self.state == InteractionState::Focused, |this| { // TODO: Add focus state
this.border()
.border_color(cx.theme().colors().border_focused)
})
.child( .child(
sized_item sized_item
.when(self.variant == ListItemVariant::Inset, |this| this.px_2()) .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
@ -425,131 +330,13 @@ impl ListEntry {
.gap_1() .gap_1()
.items_center() .items_center()
.relative() .relative()
.children(self.disclosure_control(cx)) .child(disclosure_control(self.toggle))
.children(left_content) .children(left_content)
.child(self.label), .child(self.label),
) )
} }
} }
struct ListDetailsEntryHandlers<V: 'static> {
click: Option<ClickHandler<V>>,
}
impl<V: 'static> Default for ListDetailsEntryHandlers<V> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Component)]
pub struct ListDetailsEntry<V: 'static> {
label: SharedString,
meta: Option<SharedString>,
left_content: Option<LeftContent>,
handlers: ListDetailsEntryHandlers<V>,
actions: Option<Vec<Button<V>>>,
// TODO: make this more generic instead of
// specifically for notifications
seen: bool,
}
impl<V: 'static> ListDetailsEntry<V> {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
meta: None,
left_content: None,
handlers: ListDetailsEntryHandlers::default(),
actions: None,
seen: false,
}
}
pub fn meta(mut self, meta: impl Into<SharedString>) -> Self {
self.meta = Some(meta.into());
self
}
pub fn seen(mut self, seen: bool) -> Self {
self.seen = seen;
self
}
pub fn on_click(mut self, handler: ClickHandler<V>) -> Self {
self.handlers.click = Some(handler);
self
}
pub fn actions(mut self, actions: Vec<Button<V>>) -> Self {
self.actions = Some(actions);
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let settings = user_settings(cx);
let (item_bg, item_bg_hover, item_bg_active) = (
cx.theme().colors().ghost_element,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
);
let label_color = match self.seen {
true => LabelColor::Muted,
false => LabelColor::Default,
};
div()
.relative()
.group("")
.bg(item_bg)
.px_2()
.py_1p5()
.w_full()
.z_index(1)
.when(!self.seen, |this| {
this.child(
div()
.absolute()
.left(px(3.0))
.top_3()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info),
)
})
.child(
v_stack()
.w_full()
.line_height(relative(1.2))
.gap_1()
.child(
div()
.w_5()
.h_5()
.rounded_full()
.bg(cx.theme().colors().icon_accent),
)
.child(Label::new(self.label.clone()).color(label_color))
.children(
self.meta
.map(|meta| Label::new(meta).color(LabelColor::Muted)),
)
.child(
h_stack()
.gap_1()
.justify_end()
.children(self.actions.unwrap_or_default()),
),
)
}
}
#[derive(Clone, Component)] #[derive(Clone, Component)]
pub struct ListSeparator; pub struct ListSeparator;
@ -564,20 +351,22 @@ impl ListSeparator {
} }
#[derive(Component)] #[derive(Component)]
pub struct List<V: 'static> { pub struct List {
items: Vec<ListItem<V>>, items: Vec<ListItem>,
/// Message to display when the list is empty
/// Defaults to "No items"
empty_message: SharedString, empty_message: SharedString,
header: Option<ListHeader>, header: Option<ListHeader>,
toggleable: Toggleable, toggle: Toggle,
} }
impl<V: 'static> List<V> { impl List {
pub fn new(items: Vec<ListItem<V>>) -> Self { pub fn new(items: Vec<ListItem>) -> Self {
Self { Self {
items, items,
empty_message: "No items".into(), empty_message: "No items".into(),
header: None, header: None,
toggleable: Toggleable::default(), toggle: Toggle::NotToggleable,
} }
} }
@ -591,19 +380,16 @@ impl<V: 'static> List<V> {
self self
} }
pub fn toggle(mut self, toggle: ToggleState) -> Self { pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggleable = toggle.into(); self.toggle = toggle;
self self
} }
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let is_toggleable = self.toggleable != Toggleable::NotToggleable; let list_content = match (self.items.is_empty(), self.toggle) {
let is_toggled = Toggleable::is_toggled(&self.toggleable);
let list_content = match (self.items.is_empty(), is_toggled) {
(false, _) => div().children(self.items), (false, _) => div().children(self.items),
(true, false) => div(), (true, Toggle::Toggled(false)) => div(),
(true, true) => { (true, _) => {
div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted)) div().child(Label::new(self.empty_message.clone()).color(LabelColor::Muted))
} }
}; };
@ -611,7 +397,7 @@ impl<V: 'static> List<V> {
v_stack() v_stack()
.w_full() .w_full()
.py_1() .py_1()
.children(self.header.map(|header| header.toggleable(self.toggleable))) .children(self.header.map(|header| header))
.child(list_content) .child(list_content)
} }
} }

View File

@ -2,6 +2,24 @@ use gpui2::{Hsla, ViewContext};
use crate::prelude::*; use crate::prelude::*;
/// Represents a person with a Zed account's public profile.
/// All data in this struct should be considered public.
pub struct PublicPlayer {
pub username: SharedString,
pub avatar: SharedString,
pub is_contact: bool,
}
impl PublicPlayer {
pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
Self {
username: username.into(),
avatar: avatar.into(),
is_contact: false,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum PlayerStatus { pub enum PlayerStatus {
#[default] #[default]

View File

@ -0,0 +1,14 @@
use gpui2::SharedString;
use crate::Icon;
#[derive(Debug, Clone)]
/// A slot utility that provides a way to to pass either
/// an icon or an image to a component.
///
/// Can be filled with a []
pub enum GraphicSlot {
Icon(Icon),
Avatar(SharedString),
PublicActor(SharedString),
}

View File

@ -0,0 +1,61 @@
use gpui2::{div, Component, ParentElement};
use crate::{Icon, IconColor, IconElement, IconSize};
/// Whether the entry is toggleable, and if so, whether it is currently toggled.
///
/// To make an element toggleable, simply add a `Toggle::Toggled(_)` and handle it's cases.
///
/// You can check if an element is toggleable with `.is_toggleable()`
///
/// Possible values:
/// - `Toggle::NotToggleable` - The entry is not toggleable
/// - `Toggle::Toggled(true)` - The entry is toggleable and toggled
/// - `Toggle::Toggled(false)` - The entry is toggleable and not toggled
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Toggle {
NotToggleable,
Toggled(bool),
}
impl Toggle {
/// Returns true if the entry is toggled (or is not toggleable.)
///
/// As element that isn't toggleable is always "expanded" or "enabled"
/// returning true in that case makes sense.
pub fn is_toggled(&self) -> bool {
match self {
Self::Toggled(false) => false,
_ => true,
}
}
pub fn is_toggleable(&self) -> bool {
match self {
Self::Toggled(_) => true,
_ => false,
}
}
}
impl From<bool> for Toggle {
fn from(toggled: bool) -> Self {
Toggle::Toggled(toggled)
}
}
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
match (toggle.is_toggleable(), toggle.is_toggled()) {
(false, _) => div(),
(_, true) => div().child(
IconElement::new(Icon::ChevronDown)
.color(IconColor::Muted)
.size(IconSize::Small),
),
(_, false) => div().child(
IconElement::new(Icon::ChevronRight)
.color(IconColor::Muted)
.size(IconSize::Small),
),
}
}

View File

@ -10,24 +10,6 @@ pub use theme2::ActiveTheme;
use gpui2::Hsla; use gpui2::Hsla;
use strum::EnumIter; use strum::EnumIter;
/// Represents a person with a Zed account's public profile.
/// All data in this struct should be considered public.
pub struct PublicActor {
pub username: SharedString,
pub avatar: SharedString,
pub is_contact: bool,
}
impl PublicActor {
pub fn new(username: impl Into<SharedString>, avatar: impl Into<SharedString>) -> Self {
Self {
username: username.into(),
avatar: avatar.into(),
is_contact: false,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
pub enum FileSystemStatus { pub enum FileSystemStatus {
#[default] #[default]
@ -179,61 +161,3 @@ pub enum SelectedState {
PartiallySelected, PartiallySelected,
Selected, Selected,
} }
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Toggleable {
Toggleable(ToggleState),
#[default]
NotToggleable,
}
impl Toggleable {
pub fn is_toggled(&self) -> bool {
match self {
Self::Toggleable(ToggleState::Toggled) => true,
_ => false,
}
}
}
impl From<ToggleState> for Toggleable {
fn from(state: ToggleState) -> Self {
Self::Toggleable(state)
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum ToggleState {
/// The "on" state of a toggleable element.
///
/// Example:
/// - A collasable list that is currently expanded
/// - A toggle button that is currently on.
Toggled,
/// The "off" state of a toggleable element.
///
/// Example:
/// - A collasable list that is currently collapsed
/// - A toggle button that is currently off.
#[default]
NotToggled,
}
impl From<Toggleable> for ToggleState {
fn from(toggleable: Toggleable) -> Self {
match toggleable {
Toggleable::Toggleable(state) => state,
Toggleable::NotToggleable => ToggleState::NotToggled,
}
}
}
impl From<bool> for ToggleState {
fn from(toggled: bool) -> Self {
if toggled {
ToggleState::Toggled
} else {
ToggleState::NotToggled
}
}
}

View File

@ -7,13 +7,13 @@ use gpui2::{AppContext, ViewContext};
use rand::Rng; use rand::Rng;
use theme2::ActiveTheme; use theme2::ActiveTheme;
use crate::HighlightedText;
use crate::{ use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
Livestream, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus, MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus,
PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
}; };
use crate::{HighlightedText, ListDetailsEntry};
use crate::{ListItem, NotificationAction}; use crate::{ListItem, NotificationAction};
pub fn static_tabs_example() -> Vec<Tab> { pub fn static_tabs_example() -> Vec<Tab> {
@ -345,7 +345,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z") DateTime::parse_from_rfc3339("2023-11-02T12:09:07Z")
.unwrap() .unwrap()
.naive_local(), .naive_local(),
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
[ [
NotificationAction::new( NotificationAction::new(
Button::new("Decline"), Button::new("Decline"),
@ -374,7 +374,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z") DateTime::parse_from_rfc3339("2023-11-01T12:09:07Z")
.unwrap() .unwrap()
.naive_local(), .naive_local(),
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
[ [
NotificationAction::new( NotificationAction::new(
Button::new("Decline"), Button::new("Decline"),
@ -403,7 +403,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z") DateTime::parse_from_rfc3339("2022-10-25T12:09:07Z")
.unwrap() .unwrap()
.naive_local(), .naive_local(),
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
[ [
NotificationAction::new( NotificationAction::new(
Button::new("Decline"), Button::new("Decline"),
@ -432,7 +432,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z") DateTime::parse_from_rfc3339("2021-10-12T12:09:07Z")
.unwrap() .unwrap()
.naive_local(), .naive_local(),
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
[ [
NotificationAction::new( NotificationAction::new(
Button::new("Decline"), Button::new("Decline"),
@ -461,7 +461,7 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z") DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z")
.unwrap() .unwrap()
.naive_local(), .naive_local(),
PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), PublicPlayer::new("as-cii", "http://github.com/as-cii.png?s=50"),
[ [
NotificationAction::new( NotificationAction::new(
Button::new("Decline"), Button::new("Decline"),
@ -478,89 +478,12 @@ pub fn static_new_notification_items_2<V: 'static>() -> Vec<Notification<V>> {
] ]
} }
pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> { pub fn static_project_panel_project_items() -> Vec<ListItem> {
vec![
ListItem::Header(ListSubHeader::new("New")),
ListItem::Details(
ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.")
.meta("4 people in stream."),
),
ListItem::Details(ListDetailsEntry::new(
"nathansobo accepted your contact request.",
)),
ListItem::Header(ListSubHeader::new("Earlier")),
ListItem::Details(
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
Button::new("Decline"),
Button::new("Accept").variant(crate::ButtonVariant::Filled),
]),
),
ListItem::Details(
ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
.seen(true)
.meta("This stream has ended."),
),
ListItem::Details(ListDetailsEntry::new(
"as-cii accepted your contact request.",
)),
ListItem::Details(
ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
),
ListItem::Details(ListDetailsEntry::new(
"osiewicz accepted your contact request.",
)),
ListItem::Details(ListDetailsEntry::new(
"ConradIrwin accepted your contact request.",
)),
ListItem::Details(
ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
.seen(true)
.meta("This stream has ended."),
),
ListItem::Details(ListDetailsEntry::new(
"nathansobo accepted your contact request.",
)),
ListItem::Header(ListSubHeader::new("Earlier")),
ListItem::Details(
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
Button::new("Decline"),
Button::new("Accept").variant(crate::ButtonVariant::Filled),
]),
),
ListItem::Details(
ListDetailsEntry::new("maxdeviant invited you to a stream in #design.")
.seen(true)
.meta("This stream has ended."),
),
ListItem::Details(ListDetailsEntry::new(
"as-cii accepted your contact request.",
)),
ListItem::Details(
ListDetailsEntry::new("You were added as an admin on the #gpui2 channel.").seen(true),
),
ListItem::Details(ListDetailsEntry::new(
"osiewicz accepted your contact request.",
)),
ListItem::Details(ListDetailsEntry::new(
"ConradIrwin accepted your contact request.",
)),
ListItem::Details(
ListDetailsEntry::new("nathansobo invited you to a stream in #gpui2.")
.seen(true)
.meta("This stream has ended."),
),
]
.into_iter()
.map(From::from)
.collect()
}
pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
vec![ vec![
ListEntry::new(Label::new("zed")) ListEntry::new(Label::new("zed"))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(0) .indent_level(0)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new(".cargo")) ListEntry::new(Label::new(".cargo"))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(1), .indent_level(1),
@ -579,14 +502,14 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
ListEntry::new(Label::new("assets")) ListEntry::new(Label::new("assets"))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(1) .indent_level(1)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden)) ListEntry::new(Label::new("cargo-target").color(LabelColor::Hidden))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(1), .indent_level(1),
ListEntry::new(Label::new("crates")) ListEntry::new(Label::new("crates"))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(1) .indent_level(1)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("activity_indicator")) ListEntry::new(Label::new("activity_indicator"))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(2), .indent_level(2),
@ -608,38 +531,38 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
ListEntry::new(Label::new("sqlez").color(LabelColor::Modified)) ListEntry::new(Label::new("sqlez").color(LabelColor::Modified))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(2) .indent_level(2)
.toggle(ToggleState::NotToggled), .toggle(Toggle::Toggled(false)),
ListEntry::new(Label::new("gpui2")) ListEntry::new(Label::new("gpui2"))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(2) .indent_level(2)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("src")) ListEntry::new(Label::new("src"))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(3) .indent_level(3)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("derive_element.rs")) ListEntry::new(Label::new("derive_element.rs"))
.left_icon(Icon::FileRust.into()) .left_icon(Icon::FileRust.into())
.indent_level(4), .indent_level(4),
ListEntry::new(Label::new("storybook").color(LabelColor::Modified)) ListEntry::new(Label::new("storybook").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(1) .indent_level(1)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("docs").color(LabelColor::Default)) ListEntry::new(Label::new("docs").color(LabelColor::Default))
.left_icon(Icon::Folder.into()) .left_icon(Icon::Folder.into())
.indent_level(2) .indent_level(2)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("src").color(LabelColor::Modified)) ListEntry::new(Label::new("src").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(3) .indent_level(3)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("ui").color(LabelColor::Modified)) ListEntry::new(Label::new("ui").color(LabelColor::Modified))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(4) .indent_level(4)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("component").color(LabelColor::Created)) ListEntry::new(Label::new("component").color(LabelColor::Created))
.left_icon(Icon::FolderOpen.into()) .left_icon(Icon::FolderOpen.into())
.indent_level(5) .indent_level(5)
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default)) ListEntry::new(Label::new("facepile.rs").color(LabelColor::Default))
.left_icon(Icon::FileRust.into()) .left_icon(Icon::FileRust.into())
.indent_level(6), .indent_level(6),
@ -682,7 +605,7 @@ pub fn static_project_panel_project_items<V: 'static>() -> Vec<ListItem<V>> {
.collect() .collect()
} }
pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> { pub fn static_project_panel_single_items() -> Vec<ListItem> {
vec![ vec![
ListEntry::new(Label::new("todo.md")) ListEntry::new(Label::new("todo.md"))
.left_icon(Icon::FileDoc.into()) .left_icon(Icon::FileDoc.into())
@ -699,7 +622,7 @@ pub fn static_project_panel_single_items<V: 'static>() -> Vec<ListItem<V>> {
.collect() .collect()
} }
pub fn static_collab_panel_current_call<V: 'static>() -> Vec<ListItem<V>> { pub fn static_collab_panel_current_call() -> Vec<ListItem> {
vec![ vec![
ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"), ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
ListEntry::new(Label::new("nathansobo")) ListEntry::new(Label::new("nathansobo"))
@ -712,7 +635,7 @@ pub fn static_collab_panel_current_call<V: 'static>() -> Vec<ListItem<V>> {
.collect() .collect()
} }
pub fn static_collab_panel_channels<V: 'static>() -> Vec<ListItem<V>> { pub fn static_collab_panel_channels() -> Vec<ListItem> {
vec![ vec![
ListEntry::new(Label::new("zed")) ListEntry::new(Label::new("zed"))
.left_icon(Icon::Hash.into()) .left_icon(Icon::Hash.into())

View File

@ -1,7 +1,6 @@
use crate::prelude::*; use crate::{prelude::*, Toggle};
use crate::{ use crate::{
static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, ListHeader,
ListHeader, ToggleState,
}; };
#[derive(Component)] #[derive(Component)]
@ -34,17 +33,17 @@ impl CollabPanel {
.header( .header(
ListHeader::new("CRDB") ListHeader::new("CRDB")
.left_icon(Icon::Hash.into()) .left_icon(Icon::Hash.into())
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
) )
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
), ),
) )
.child( .child(
v_stack().id("channels").py_1().child( v_stack().id("channels").py_1().child(
List::new(static_collab_panel_channels()) List::new(static_collab_panel_channels())
.header(ListHeader::new("CHANNELS").toggle(ToggleState::Toggled)) .header(ListHeader::new("CHANNELS").toggle(Toggle::Toggled(true)))
.empty_message("No channels yet. Add a channel to get started.") .empty_message("No channels yet. Add a channel to get started.")
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
), ),
) )
.child( .child(
@ -52,9 +51,9 @@ impl CollabPanel {
List::new(static_collab_panel_current_call()) List::new(static_collab_panel_current_call())
.header( .header(
ListHeader::new("CONTACTS ONLINE") ListHeader::new("CONTACTS ONLINE")
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
) )
.toggle(ToggleState::Toggled), .toggle(Toggle::Toggled(true)),
), ),
) )
.child( .child(
@ -62,9 +61,9 @@ impl CollabPanel {
List::new(static_collab_panel_current_call()) List::new(static_collab_panel_current_call())
.header( .header(
ListHeader::new("CONTACTS OFFLINE") ListHeader::new("CONTACTS OFFLINE")
.toggle(ToggleState::NotToggled), .toggle(Toggle::Toggled(false)),
) )
.toggle(ToggleState::NotToggled), .toggle(Toggle::Toggled(false)),
), ),
), ),
) )

View File

@ -1,8 +1,8 @@
use crate::utils::naive_format_distance_from_now; use crate::utils::naive_format_distance_from_now;
use crate::{ use crate::{
h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, Button, Icon, h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, ButtonOrIconButton,
IconButton, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, Icon, IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator,
UnreadIndicator, PublicPlayer, UnreadIndicator,
}; };
use crate::{ClickHandler, ListHeader}; use crate::{ClickHandler, ListHeader};
@ -57,23 +57,6 @@ impl NotificationsPanel {
} }
} }
pub enum ButtonOrIconButton<V: 'static> {
Button(Button<V>),
IconButton(IconButton<V>),
}
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> Self {
Self::IconButton(value)
}
}
pub struct NotificationAction<V: 'static> { pub struct NotificationAction<V: 'static> {
button: ButtonOrIconButton<V>, button: ButtonOrIconButton<V>,
tooltip: SharedString, tooltip: SharedString,
@ -102,7 +85,7 @@ impl<V: 'static> NotificationAction<V> {
} }
pub enum ActorOrIcon { pub enum ActorOrIcon {
Actor(PublicActor), Actor(PublicPlayer),
Icon(Icon), Icon(Icon),
} }
@ -171,7 +154,7 @@ impl<V> Notification<V> {
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: impl Into<SharedString>, message: impl Into<SharedString>,
date_received: NaiveDateTime, date_received: NaiveDateTime,
actor: PublicActor, actor: PublicPlayer,
click_action: ClickHandler<V>, click_action: ClickHandler<V>,
) -> Self { ) -> Self {
Self::new( Self::new(
@ -210,7 +193,7 @@ impl<V> Notification<V> {
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: impl Into<SharedString>, message: impl Into<SharedString>,
date_received: NaiveDateTime, date_received: NaiveDateTime,
actor: PublicActor, actor: PublicPlayer,
actions: [NotificationAction<V>; 2], actions: [NotificationAction<V>; 2],
) -> Self { ) -> Self {
Self::new( Self::new(

View File

@ -18,8 +18,7 @@ impl ProjectPanel {
.id(self.id.clone()) .id(self.id.clone())
.flex() .flex()
.flex_col() .flex_col()
.w_full() .size_full()
.h_full()
.bg(cx.theme().colors().surface) .bg(cx.theme().colors().surface)
.child( .child(
div() div()
@ -30,15 +29,13 @@ impl ProjectPanel {
.overflow_y_scroll() .overflow_y_scroll()
.child( .child(
List::new(static_project_panel_single_items()) List::new(static_project_panel_single_items())
.header(ListHeader::new("FILES").toggle(ToggleState::Toggled)) .header(ListHeader::new("FILES"))
.empty_message("No files in directory") .empty_message("No files in directory"),
.toggle(ToggleState::Toggled),
) )
.child( .child(
List::new(static_project_panel_project_items()) List::new(static_project_panel_project_items())
.header(ListHeader::new("PROJECT").toggle(ToggleState::Toggled)) .header(ListHeader::new("PROJECT"))
.empty_message("No folders in directory") .empty_message("No folders in directory"),
.toggle(ToggleState::Toggled),
), ),
) )
.child( .child(