Show badge when there are pending contact requests
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
c71b264786
commit
933a1f2cd6
17 changed files with 241 additions and 42 deletions
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#efecf4",
|
||||
"background": "#5852605c"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#26232a"
|
||||
},
|
||||
"background": "#576ddb"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#19171c",
|
||||
"background": "#8b87922e"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#e2dfe7"
|
||||
},
|
||||
"background": "#576ddb"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#ffffff",
|
||||
"background": "#2b2b2b"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#1c1c1c"
|
||||
},
|
||||
"background": "#2472f2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#000000",
|
||||
"background": "#e3e3e3"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#f8f8f8"
|
||||
},
|
||||
"background": "#484bed"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#fdf6e3",
|
||||
"background": "#586e755c"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#073642"
|
||||
},
|
||||
"background": "#268bd2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#002b36",
|
||||
"background": "#93a1a12e"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#eee8d5"
|
||||
},
|
||||
"background": "#268bd2"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#f5f7ff",
|
||||
"background": "#5e66875c"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#293256"
|
||||
},
|
||||
"background": "#3d8fd1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -341,6 +341,19 @@
|
|||
"icon_color": "#202746",
|
||||
"background": "#979db42e"
|
||||
}
|
||||
},
|
||||
"badge": {
|
||||
"corner_radius": 3,
|
||||
"padding": 2,
|
||||
"margin": {
|
||||
"bottom": -1,
|
||||
"right": -1
|
||||
},
|
||||
"border": {
|
||||
"width": 1,
|
||||
"color": "#dfe2f1"
|
||||
},
|
||||
"background": "#3d8fd1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7264,7 +7264,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
|
||||
gpui::Element::boxed(gpui::elements::Empty)
|
||||
gpui::Element::boxed(gpui::elements::Empty::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ use gpui::{
|
|||
geometry::{rect::RectF, vector::vec2f},
|
||||
impl_actions,
|
||||
platform::CursorStyle,
|
||||
Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext, RenderContext,
|
||||
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
AppContext, Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext,
|
||||
RenderContext, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::IconButton;
|
||||
use workspace::{AppState, JoinProject, Workspace};
|
||||
use workspace::{sidebar::SidebarItem, AppState, JoinProject, Workspace};
|
||||
|
||||
impl_actions!(
|
||||
contacts_panel,
|
||||
|
@ -599,6 +599,16 @@ impl ContactsPanel {
|
|||
}
|
||||
}
|
||||
|
||||
impl SidebarItem for ContactsPanel {
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool {
|
||||
!self
|
||||
.user_store
|
||||
.read(cx)
|
||||
.incoming_contact_requests()
|
||||
.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
|
||||
Svg::new(svg_path)
|
||||
.with_color(style.color)
|
||||
|
|
|
@ -8,11 +8,18 @@ use crate::{
|
|||
};
|
||||
use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
|
||||
|
||||
pub struct Empty;
|
||||
pub struct Empty {
|
||||
collapsed: bool,
|
||||
}
|
||||
|
||||
impl Empty {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
Self { collapsed: false }
|
||||
}
|
||||
|
||||
pub fn collapsed(mut self) -> Self {
|
||||
self.collapsed = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,12 +32,12 @@ impl Element for Empty {
|
|||
constraint: SizeConstraint,
|
||||
_: &mut LayoutContext,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let x = if constraint.max.x().is_finite() {
|
||||
let x = if constraint.max.x().is_finite() && !self.collapsed {
|
||||
constraint.max.x()
|
||||
} else {
|
||||
constraint.min.x()
|
||||
};
|
||||
let y = if constraint.max.y().is_finite() {
|
||||
let y = if constraint.max.y().is_finite() && !self.collapsed {
|
||||
constraint.max.y()
|
||||
} else {
|
||||
constraint.min.y()
|
||||
|
|
|
@ -900,6 +900,12 @@ impl Entity for ProjectPanel {
|
|||
type Event = Event;
|
||||
}
|
||||
|
||||
impl workspace::sidebar::SidebarItem for ProjectPanel {
|
||||
fn should_show_badge(&self, _: &AppContext) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -162,6 +162,7 @@ pub struct StatusBarSidebarButtons {
|
|||
pub group_left: ContainerStyle,
|
||||
pub group_right: ContainerStyle,
|
||||
pub item: Interactive<SidebarItem>,
|
||||
pub badge: ContainerStyle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
|
|
|
@ -1,13 +1,40 @@
|
|||
use crate::StatusItemView;
|
||||
use gpui::{
|
||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, Entity, RenderContext, View,
|
||||
ViewContext, ViewHandle,
|
||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
|
||||
RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use theme::Theme;
|
||||
|
||||
use crate::StatusItemView;
|
||||
pub trait SidebarItem: View {
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool;
|
||||
}
|
||||
|
||||
pub trait SidebarItemHandle {
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool;
|
||||
fn to_any(&self) -> AnyViewHandle;
|
||||
}
|
||||
|
||||
impl<T> SidebarItemHandle for ViewHandle<T>
|
||||
where
|
||||
T: SidebarItem,
|
||||
{
|
||||
fn should_show_badge(&self, cx: &AppContext) -> bool {
|
||||
self.read(cx).should_show_badge(cx)
|
||||
}
|
||||
|
||||
fn to_any(&self) -> AnyViewHandle {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<AnyViewHandle> for &dyn SidebarItemHandle {
|
||||
fn into(self) -> AnyViewHandle {
|
||||
self.to_any()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sidebar {
|
||||
side: Side,
|
||||
|
@ -23,10 +50,10 @@ pub enum Side {
|
|||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Item {
|
||||
icon_path: &'static str,
|
||||
view: AnyViewHandle,
|
||||
view: Rc<dyn SidebarItemHandle>,
|
||||
_observation: Subscription,
|
||||
}
|
||||
|
||||
pub struct SidebarButtons {
|
||||
|
@ -58,13 +85,18 @@ impl Sidebar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_item(
|
||||
pub fn add_item<T: SidebarItem>(
|
||||
&mut self,
|
||||
icon_path: &'static str,
|
||||
view: AnyViewHandle,
|
||||
view: ViewHandle<T>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.items.push(Item { icon_path, view });
|
||||
let subscription = cx.observe(&view, |_, _, cx| cx.notify());
|
||||
self.items.push(Item {
|
||||
icon_path,
|
||||
view: Rc::new(view),
|
||||
_observation: subscription,
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
|
@ -82,10 +114,10 @@ impl Sidebar {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn active_item(&self) -> Option<&AnyViewHandle> {
|
||||
pub fn active_item(&self) -> Option<&dyn SidebarItemHandle> {
|
||||
self.active_item_ix
|
||||
.and_then(|ix| self.items.get(ix))
|
||||
.map(|item| &item.view)
|
||||
.map(|item| item.view.as_ref())
|
||||
}
|
||||
|
||||
fn render_resize_handle(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
|
@ -185,34 +217,62 @@ impl View for SidebarButtons {
|
|||
.sidebar_buttons;
|
||||
let sidebar = self.sidebar.read(cx);
|
||||
let item_style = theme.item;
|
||||
let badge_style = theme.badge;
|
||||
let active_ix = sidebar.active_item_ix;
|
||||
let side = sidebar.side;
|
||||
let group_style = match side {
|
||||
Side::Left => theme.group_left,
|
||||
Side::Right => theme.group_right,
|
||||
};
|
||||
let items = sidebar.items.clone();
|
||||
let items = sidebar
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| (item.icon_path, item.view.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
Flex::row()
|
||||
.with_children(items.iter().enumerate().map(|(ix, item)| {
|
||||
MouseEventHandler::new::<Self, _, _>(ix, cx, move |state, _| {
|
||||
let style = item_style.style_for(state, Some(ix) == active_ix);
|
||||
Svg::new(item.icon_path)
|
||||
.with_color(style.icon_color)
|
||||
.constrained()
|
||||
.with_height(style.icon_size)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.with_children(
|
||||
items
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, (icon_path, item_view))| {
|
||||
MouseEventHandler::new::<Self, _, _>(ix, cx, move |state, cx| {
|
||||
let is_active = Some(ix) == active_ix;
|
||||
let style = item_style.style_for(state, is_active);
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Svg::new(icon_path).with_color(style.icon_color).boxed(),
|
||||
)
|
||||
.with_children(if !is_active && item_view.should_show_badge(cx) {
|
||||
Some(
|
||||
Empty::new()
|
||||
.collapsed()
|
||||
.contained()
|
||||
.with_style(badge_style)
|
||||
.aligned()
|
||||
.bottom()
|
||||
.right()
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.constrained()
|
||||
.with_width(style.icon_size)
|
||||
.with_height(style.icon_size)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |_, cx| {
|
||||
cx.dispatch_action(ToggleSidebarItem {
|
||||
side,
|
||||
item_index: ix,
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(move |_, cx| {
|
||||
cx.dispatch_action(ToggleSidebarItem {
|
||||
side,
|
||||
item_index: ix,
|
||||
})
|
||||
})
|
||||
.boxed()
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.contained()
|
||||
.with_style(group_style)
|
||||
.boxed()
|
||||
|
|
|
@ -1102,7 +1102,7 @@ impl Workspace {
|
|||
};
|
||||
let active_item = sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.toggle_item(action.item_index, cx);
|
||||
sidebar.active_item().cloned()
|
||||
sidebar.active_item().map(|item| item.to_any())
|
||||
});
|
||||
if let Some(active_item) = active_item {
|
||||
cx.focus(active_item);
|
||||
|
@ -1123,7 +1123,7 @@ impl Workspace {
|
|||
};
|
||||
let active_item = sidebar.update(cx, |sidebar, cx| {
|
||||
sidebar.activate_item(action.item_index, cx);
|
||||
sidebar.active_item().cloned()
|
||||
sidebar.active_item().map(|item| item.to_any())
|
||||
});
|
||||
if let Some(active_item) = active_item {
|
||||
if active_item.is_focused(cx) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Theme from "../themes/theme";
|
||||
import { backgroundColor, border, iconColor, text } from "./components";
|
||||
import { workspaceBackground } from "./workspace";
|
||||
|
||||
export default function statusBar(theme: Theme) {
|
||||
|
||||
const statusContainer = {
|
||||
cornerRadius: 6,
|
||||
padding: { top: 3, bottom: 3, left: 6, right: 6 }
|
||||
|
@ -100,6 +100,13 @@ export default function statusBar(theme: Theme) {
|
|||
iconColor: iconColor(theme, "active"),
|
||||
background: backgroundColor(theme, 300, "active"),
|
||||
}
|
||||
},
|
||||
badge: {
|
||||
cornerRadius: 3,
|
||||
padding: 2,
|
||||
margin: { bottom: -1, right: -1 },
|
||||
border: { width: 1, color: workspaceBackground(theme) },
|
||||
background: iconColor(theme, "feature"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,15 @@ import Theme from "../themes/theme";
|
|||
import { backgroundColor, border, iconColor, shadow, text } from "./components";
|
||||
import statusBar from "./statusBar";
|
||||
|
||||
export function workspaceBackground(theme: Theme) {
|
||||
return backgroundColor(theme, 300)
|
||||
}
|
||||
|
||||
export default function workspace(theme: Theme) {
|
||||
|
||||
const tab = {
|
||||
height: 32,
|
||||
background: backgroundColor(theme, 300),
|
||||
background: workspaceBackground(theme),
|
||||
iconClose: iconColor(theme, "muted"),
|
||||
iconCloseActive: iconColor(theme, "active"),
|
||||
iconConflict: iconColor(theme, "warning"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue