mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-24 17:28:40 +00:00
Rearrange collab titlebar items to avoid movement of the toggle contacts button
* Replace username in titelbar with a `...` user menu that shows the current user name and contains a sign-in/sign-out button. * Move the '+' (toggle contacts) button back to the right side. * Move the collaborators back to the right side. * Move the share/unshare button to the left side, beside the project title * Only show the share/unshare button when in a call.
This commit is contained in:
parent
24e0a027ee
commit
bf5c3d963a
8 changed files with 183 additions and 156 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1257,6 +1257,7 @@ dependencies = [
|
|||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"editor",
|
||||
"futures 0.3.25",
|
||||
"fuzzy",
|
||||
|
|
3
assets/icons/ellipsis_14.svg
Normal file
3
assets/icons/ellipsis_14.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="4" viewBox="0 0 14 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 2C3.125 2.62132 2.62132 3.125 2 3.125C1.37868 3.125 0.875 2.62132 0.875 2C0.875 1.37868 1.37868 0.875 2 0.875C2.62132 0.875 3.125 1.37868 3.125 2ZM8.125 2C8.125 2.62132 7.62132 3.125 7 3.125C6.37868 3.125 5.875 2.62132 5.875 2C5.875 1.37868 6.37868 0.875 7 0.875C7.62132 0.875 8.125 1.37868 8.125 2ZM12 3.125C12.6213 3.125 13.125 2.62132 13.125 2C13.125 1.37868 12.6213 0.875 12 0.875C11.3787 0.875 10.875 1.37868 10.875 2C10.875 2.62132 11.3787 3.125 12 3.125Z" fill="#ABB2BF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 637 B |
|
@ -66,7 +66,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
|||
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(client, [Authenticate]);
|
||||
actions!(client, [Authenticate, SignOut]);
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
|
||||
cx.add_global_action({
|
||||
|
@ -79,6 +79,16 @@ pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
|
|||
.detach();
|
||||
}
|
||||
});
|
||||
cx.add_global_action({
|
||||
let client = client.clone();
|
||||
move |_: &SignOut, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
client.set_status(Status::SignedOut, &cx);
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
|
|
|
@ -27,6 +27,7 @@ call = { path = "../call" }
|
|||
client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
editor = { path = "../editor" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
|
|
|
@ -4,9 +4,10 @@ use crate::{
|
|||
ToggleScreenSharing,
|
||||
};
|
||||
use call::{ActiveCall, ParticipantLocation, Room};
|
||||
use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore};
|
||||
use client::{proto::PeerId, Authenticate, ContactEventKind, SignOut, User, UserStore};
|
||||
use clock::ReplicaId;
|
||||
use contacts_popover::ContactsPopover;
|
||||
use context_menu::{ContextMenu, ContextMenuItem};
|
||||
use gpui::{
|
||||
actions,
|
||||
color::Color,
|
||||
|
@ -28,8 +29,9 @@ actions!(
|
|||
[
|
||||
ToggleCollaboratorList,
|
||||
ToggleContactsMenu,
|
||||
ToggleUserMenu,
|
||||
ShareProject,
|
||||
UnshareProject
|
||||
UnshareProject,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -38,25 +40,20 @@ impl_internal_actions!(collab, [LeaveCall]);
|
|||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub(crate) struct LeaveCall;
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum ContactsPopoverSide {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
|
||||
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
|
||||
cx.add_action(CollabTitlebarItem::share_project);
|
||||
cx.add_action(CollabTitlebarItem::unshare_project);
|
||||
cx.add_action(CollabTitlebarItem::leave_call);
|
||||
cx.add_action(CollabTitlebarItem::toggle_user_menu);
|
||||
}
|
||||
|
||||
pub struct CollabTitlebarItem {
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
contacts_popover: Option<ViewHandle<ContactsPopover>>,
|
||||
contacts_popover_side: ContactsPopoverSide,
|
||||
user_menu: ViewHandle<ContextMenu>,
|
||||
collaborator_list_popover: Option<ViewHandle<CollaboratorListPopover>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
@ -93,6 +90,7 @@ impl View for CollabTitlebarItem {
|
|||
let user = workspace.read(cx).user_store().read(cx).current_user();
|
||||
|
||||
let mut left_container = Flex::row();
|
||||
let mut right_container = Flex::row();
|
||||
|
||||
left_container.add_child(
|
||||
Label::new(project_title, theme.workspace.titlebar.title.clone())
|
||||
|
@ -104,40 +102,25 @@ impl View for CollabTitlebarItem {
|
|||
);
|
||||
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
left_container.add_child(self.render_current_user(&workspace, &theme, &user, cx));
|
||||
left_container.add_children(self.render_collaborators(&workspace, &theme, room, cx));
|
||||
left_container.add_child(self.render_toggle_contacts_button(&theme, cx));
|
||||
}
|
||||
left_container
|
||||
.add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
|
||||
|
||||
let mut right_container = Flex::row();
|
||||
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
right_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
|
||||
right_container.add_child(self.render_current_user(&workspace, &theme, &user, cx));
|
||||
right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
|
||||
right_container.add_child(self.render_leave_call_button(&theme, cx));
|
||||
right_container
|
||||
.add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
|
||||
} else {
|
||||
right_container.add_child(self.render_outside_call_share_button(&theme, cx));
|
||||
}
|
||||
|
||||
right_container.add_children(self.render_connection_status(&workspace, cx));
|
||||
|
||||
if let Some(user) = user {
|
||||
//TODO: Add style
|
||||
right_container.add_child(
|
||||
Label::new(
|
||||
user.github_login.clone(),
|
||||
theme.workspace.titlebar.title.clone(),
|
||||
)
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(theme.workspace.titlebar.item_spacing)
|
||||
.boxed(),
|
||||
);
|
||||
let status = workspace.read(cx).client().status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
right_container.add_child(self.render_toggle_contacts_button(&theme, cx));
|
||||
} else {
|
||||
right_container.add_child(Self::render_authenticate(&theme, cx));
|
||||
right_container.add_children(self.render_connection_status(status, cx));
|
||||
}
|
||||
|
||||
right_container.add_child(self.render_user_menu_button(&theme, cx));
|
||||
|
||||
Stack::new()
|
||||
.with_child(left_container.boxed())
|
||||
.with_child(right_container.aligned().right().boxed())
|
||||
|
@ -186,7 +169,11 @@ impl CollabTitlebarItem {
|
|||
workspace: workspace.downgrade(),
|
||||
user_store: user_store.clone(),
|
||||
contacts_popover: None,
|
||||
contacts_popover_side: ContactsPopoverSide::Right,
|
||||
user_menu: cx.add_view(|cx| {
|
||||
let mut menu = ContextMenu::new(cx);
|
||||
menu.set_position_mode(OverlayPositionMode::Local);
|
||||
menu
|
||||
}),
|
||||
collaborator_list_popover: None,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
|
@ -278,12 +265,6 @@ impl CollabTitlebarItem {
|
|||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
|
||||
self.contacts_popover_side = match ActiveCall::global(cx).read(cx).room() {
|
||||
Some(_) => ContactsPopoverSide::Left,
|
||||
None => ContactsPopoverSide::Right,
|
||||
};
|
||||
|
||||
self.contacts_popover = Some(view);
|
||||
}
|
||||
}
|
||||
|
@ -291,6 +272,44 @@ impl CollabTitlebarItem {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let label_style = theme.context_menu.item.disabled_style().label.clone();
|
||||
self.user_menu.update(cx, |user_menu, cx| {
|
||||
let items = if let Some(user) = self.user_store.read(cx).current_user() {
|
||||
vec![
|
||||
ContextMenuItem::Static(Box::new(move |_| {
|
||||
Label::new(user.github_login.clone(), label_style.clone()).boxed()
|
||||
})),
|
||||
ContextMenuItem::Item {
|
||||
label: "Sign out".into(),
|
||||
action: Box::new(SignOut),
|
||||
},
|
||||
]
|
||||
} else {
|
||||
vec![ContextMenuItem::Item {
|
||||
label: "Sign in".into(),
|
||||
action: Box::new(Authenticate),
|
||||
}]
|
||||
};
|
||||
|
||||
user_menu.show(
|
||||
vec2f(
|
||||
theme
|
||||
.workspace
|
||||
.titlebar
|
||||
.user_menu_button
|
||||
.default
|
||||
.button_width,
|
||||
theme.workspace.titlebar.height,
|
||||
),
|
||||
AnchorCorner::TopRight,
|
||||
items,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
|
@ -328,11 +347,9 @@ impl CollabTitlebarItem {
|
|||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
|
||||
let style = titlebar.toggle_contacts_button.style_for(
|
||||
state,
|
||||
self.contacts_popover.is_some()
|
||||
&& self.contacts_popover_side == ContactsPopoverSide::Left,
|
||||
);
|
||||
let style = titlebar
|
||||
.toggle_contacts_button
|
||||
.style_for(state, self.contacts_popover.is_some());
|
||||
Svg::new("icons/plus_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
|
@ -360,11 +377,7 @@ impl CollabTitlebarItem {
|
|||
.boxed(),
|
||||
)
|
||||
.with_children(badge)
|
||||
.with_children(self.render_contacts_popover_host(
|
||||
ContactsPopoverSide::Left,
|
||||
titlebar,
|
||||
cx,
|
||||
))
|
||||
.with_children(self.render_contacts_popover_host(titlebar, cx))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
@ -475,11 +488,9 @@ impl CollabTitlebarItem {
|
|||
.with_child(
|
||||
MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistant width for both text variations
|
||||
let style = titlebar.share_button.style_for(
|
||||
state,
|
||||
self.contacts_popover.is_some()
|
||||
&& self.contacts_popover_side == ContactsPopoverSide::Right,
|
||||
);
|
||||
let style = titlebar
|
||||
.share_button
|
||||
.style_for(state, self.contacts_popover.is_some());
|
||||
Label::new(label, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
|
@ -502,11 +513,6 @@ impl CollabTitlebarItem {
|
|||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_children(self.render_contacts_popover_host(
|
||||
ContactsPopoverSide::Right,
|
||||
titlebar,
|
||||
cx,
|
||||
))
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(theme.workspace.titlebar.item_spacing)
|
||||
|
@ -514,83 +520,71 @@ impl CollabTitlebarItem {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_outside_call_share_button(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> ElementBox {
|
||||
let tooltip = "Share project with new call";
|
||||
fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let titlebar = &theme.workspace.titlebar;
|
||||
|
||||
enum OutsideCallShare {}
|
||||
Stack::new()
|
||||
.with_child(
|
||||
MouseEventHandler::<OutsideCallShare>::new(0, cx, |state, _| {
|
||||
//TODO: Ensure this button has consistant width for both text variations
|
||||
let style = titlebar.share_button.style_for(
|
||||
state,
|
||||
self.contacts_popover.is_some()
|
||||
&& self.contacts_popover_side == ContactsPopoverSide::Right,
|
||||
);
|
||||
Label::new("Share".to_owned(), style.text.clone())
|
||||
MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
|
||||
let style = titlebar.call_control.style_for(state, false);
|
||||
Svg::new("icons/ellipsis_14.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
.aligned()
|
||||
.constrained()
|
||||
.with_width(style.button_width)
|
||||
.with_height(style.button_width)
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ToggleContactsMenu);
|
||||
cx.dispatch_action(ToggleUserMenu);
|
||||
})
|
||||
.with_tooltip::<OutsideCallShare, _>(
|
||||
.with_tooltip::<ToggleUserMenu, _>(
|
||||
0,
|
||||
tooltip.to_owned(),
|
||||
None,
|
||||
"Toggle user menu".to_owned(),
|
||||
Some(Box::new(ToggleUserMenu)),
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.contained()
|
||||
.with_margin_left(theme.workspace.titlebar.item_spacing)
|
||||
.aligned()
|
||||
.boxed(),
|
||||
)
|
||||
.with_children(self.render_contacts_popover_host(
|
||||
ContactsPopoverSide::Right,
|
||||
titlebar,
|
||||
cx,
|
||||
))
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_margin_left(theme.workspace.titlebar.item_spacing)
|
||||
.with_child(ChildView::new(&self.user_menu, cx).boxed())
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_contacts_popover_host<'a>(
|
||||
&'a self,
|
||||
side: ContactsPopoverSide,
|
||||
theme: &'a theme::Titlebar,
|
||||
cx: &'a RenderContext<Self>,
|
||||
) -> impl Iterator<Item = ElementBox> + 'a {
|
||||
self.contacts_popover
|
||||
.iter()
|
||||
.filter(move |_| self.contacts_popover_side == side)
|
||||
.map(|popover| {
|
||||
Overlay::new(
|
||||
ChildView::new(popover, cx)
|
||||
.contained()
|
||||
.with_margin_top(theme.height)
|
||||
.with_margin_left(theme.toggle_contacts_button.default.button_width)
|
||||
.with_margin_right(-theme.toggle_contacts_button.default.button_width)
|
||||
.boxed(),
|
||||
)
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_corner(AnchorCorner::BottomLeft)
|
||||
.with_z_index(999)
|
||||
.boxed()
|
||||
})
|
||||
) -> Option<ElementBox> {
|
||||
self.contacts_popover.as_ref().map(|popover| {
|
||||
Overlay::new(
|
||||
ChildView::new(popover, cx)
|
||||
.contained()
|
||||
.with_margin_top(theme.height)
|
||||
.with_margin_left(theme.toggle_contacts_button.default.button_width)
|
||||
.with_margin_right(-theme.toggle_contacts_button.default.button_width)
|
||||
.boxed(),
|
||||
)
|
||||
.with_fit_mode(OverlayFitMode::SwitchAnchor)
|
||||
.with_anchor_corner(AnchorCorner::BottomLeft)
|
||||
.with_z_index(999)
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_collaborators(
|
||||
&self,
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
theme: &Theme,
|
||||
room: ModelHandle<Room>,
|
||||
room: &ModelHandle<Room>,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Vec<ElementBox> {
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
|
@ -622,7 +616,7 @@ impl CollabTitlebarItem {
|
|||
theme,
|
||||
cx,
|
||||
))
|
||||
.with_margin_left(theme.workspace.titlebar.face_pile_spacing)
|
||||
.with_margin_right(theme.workspace.titlebar.face_pile_spacing)
|
||||
.boxed(),
|
||||
)
|
||||
})
|
||||
|
@ -643,25 +637,16 @@ impl CollabTitlebarItem {
|
|||
.client()
|
||||
.peer_id()
|
||||
.expect("Active call without peer id");
|
||||
self.render_face_pile(user, Some(replica_id), peer_id, None, workspace, theme, cx)
|
||||
}
|
||||
|
||||
fn render_authenticate(theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
MouseEventHandler::<Authenticate>::new(0, cx, |state, _| {
|
||||
let style = theme
|
||||
.workspace
|
||||
.titlebar
|
||||
.sign_in_prompt
|
||||
.style_for(state, false);
|
||||
Label::new("Sign in", style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
.with_margin_left(theme.workspace.titlebar.item_spacing)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.aligned()
|
||||
Container::new(self.render_face_pile(
|
||||
user,
|
||||
Some(replica_id),
|
||||
peer_id,
|
||||
None,
|
||||
workspace,
|
||||
theme,
|
||||
cx,
|
||||
))
|
||||
.with_margin_right(theme.workspace.titlebar.item_spacing)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
@ -717,7 +702,7 @@ impl CollabTitlebarItem {
|
|||
}
|
||||
}
|
||||
|
||||
let content = Stack::new()
|
||||
let mut content = Stack::new()
|
||||
.with_children(user.avatar.as_ref().map(|avatar| {
|
||||
let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap)
|
||||
.with_child(Self::render_face(
|
||||
|
@ -789,7 +774,10 @@ impl CollabTitlebarItem {
|
|||
|
||||
if let Some(location) = location {
|
||||
if let Some(replica_id) = replica_id {
|
||||
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| content)
|
||||
content =
|
||||
MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
|
||||
content
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ToggleFollow(peer_id))
|
||||
|
@ -805,12 +793,14 @@ impl CollabTitlebarItem {
|
|||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.boxed()
|
||||
.boxed();
|
||||
} else if let ParticipantLocation::SharedProject { project_id } = location {
|
||||
let user_id = user.id;
|
||||
MouseEventHandler::<JoinProject>::new(peer_id.as_u64() as usize, cx, move |_, _| {
|
||||
content
|
||||
})
|
||||
content = MouseEventHandler::<JoinProject>::new(
|
||||
peer_id.as_u64() as usize,
|
||||
cx,
|
||||
move |_, _| content,
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(JoinProject {
|
||||
|
@ -825,13 +815,10 @@ impl CollabTitlebarItem {
|
|||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.boxed()
|
||||
} else {
|
||||
content
|
||||
.boxed();
|
||||
}
|
||||
} else {
|
||||
content
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
fn render_face(
|
||||
|
@ -854,13 +841,13 @@ impl CollabTitlebarItem {
|
|||
|
||||
fn render_connection_status(
|
||||
&self,
|
||||
workspace: &ViewHandle<Workspace>,
|
||||
status: &client::Status,
|
||||
cx: &mut RenderContext<Self>,
|
||||
) -> Option<ElementBox> {
|
||||
enum ConnectionStatusButton {}
|
||||
|
||||
let theme = &cx.global::<Settings>().theme.clone();
|
||||
match &*workspace.read(cx).client().status().borrow() {
|
||||
match status {
|
||||
client::Status::ConnectionError
|
||||
| client::Status::ConnectionLost
|
||||
| client::Status::Reauthenticating { .. }
|
||||
|
|
|
@ -5,7 +5,9 @@ use gpui::{
|
|||
};
|
||||
use menu::*;
|
||||
use settings::Settings;
|
||||
use std::{any::TypeId, time::Duration};
|
||||
use std::{any::TypeId, borrow::Cow, time::Duration};
|
||||
|
||||
pub type StaticItem = Box<dyn Fn(&mut MutableAppContext) -> ElementBox>;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
struct Clicked;
|
||||
|
@ -24,16 +26,17 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
|
||||
pub enum ContextMenuItem {
|
||||
Item {
|
||||
label: String,
|
||||
label: Cow<'static, str>,
|
||||
action: Box<dyn Action>,
|
||||
},
|
||||
Static(StaticItem),
|
||||
Separator,
|
||||
}
|
||||
|
||||
impl ContextMenuItem {
|
||||
pub fn item(label: impl ToString, action: impl 'static + Action) -> Self {
|
||||
pub fn item(label: impl Into<Cow<'static, str>>, action: impl 'static + Action) -> Self {
|
||||
Self::Item {
|
||||
label: label.to_string(),
|
||||
label: label.into(),
|
||||
action: Box::new(action),
|
||||
}
|
||||
}
|
||||
|
@ -42,14 +45,14 @@ impl ContextMenuItem {
|
|||
Self::Separator
|
||||
}
|
||||
|
||||
fn is_separator(&self) -> bool {
|
||||
matches!(self, Self::Separator)
|
||||
fn is_action(&self) -> bool {
|
||||
matches!(self, Self::Item { .. })
|
||||
}
|
||||
|
||||
fn action_id(&self) -> Option<TypeId> {
|
||||
match self {
|
||||
ContextMenuItem::Item { action, .. } => Some(action.id()),
|
||||
ContextMenuItem::Separator => None,
|
||||
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +61,7 @@ pub struct ContextMenu {
|
|||
show_count: usize,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
position_mode: OverlayPositionMode,
|
||||
items: Vec<ContextMenuItem>,
|
||||
selected_index: Option<usize>,
|
||||
visible: bool,
|
||||
|
@ -105,6 +109,7 @@ impl View for ContextMenu {
|
|||
.with_fit_mode(OverlayFitMode::SnapToWindow)
|
||||
.with_anchor_position(self.anchor_position)
|
||||
.with_anchor_corner(self.anchor_corner)
|
||||
.with_position_mode(self.position_mode)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
@ -121,6 +126,7 @@ impl ContextMenu {
|
|||
show_count: 0,
|
||||
anchor_position: Default::default(),
|
||||
anchor_corner: AnchorCorner::TopLeft,
|
||||
position_mode: OverlayPositionMode::Window,
|
||||
items: Default::default(),
|
||||
selected_index: Default::default(),
|
||||
visible: Default::default(),
|
||||
|
@ -188,13 +194,13 @@ impl ContextMenu {
|
|||
}
|
||||
|
||||
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
|
||||
self.selected_index = self.items.iter().position(|item| !item.is_separator());
|
||||
self.selected_index = self.items.iter().position(|item| item.is_action());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
|
||||
for (ix, item) in self.items.iter().enumerate().rev() {
|
||||
if !item.is_separator() {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
|
@ -205,7 +211,7 @@ impl ContextMenu {
|
|||
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
|
||||
if !item.is_separator() {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
|
@ -219,7 +225,7 @@ impl ContextMenu {
|
|||
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
|
||||
if let Some(ix) = self.selected_index {
|
||||
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
|
||||
if !item.is_separator() {
|
||||
if item.is_action() {
|
||||
self.selected_index = Some(ix);
|
||||
cx.notify();
|
||||
break;
|
||||
|
@ -234,7 +240,7 @@ impl ContextMenu {
|
|||
&mut self,
|
||||
anchor_position: Vector2F,
|
||||
anchor_corner: AnchorCorner,
|
||||
items: impl IntoIterator<Item = ContextMenuItem>,
|
||||
items: Vec<ContextMenuItem>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let mut items = items.into_iter().peekable();
|
||||
|
@ -254,6 +260,10 @@ impl ContextMenu {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
|
||||
self.position_mode = mode;
|
||||
}
|
||||
|
||||
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
|
||||
let window_id = cx.window_id();
|
||||
let style = cx.global::<Settings>().theme.context_menu.clone();
|
||||
|
@ -273,6 +283,9 @@ impl ContextMenu {
|
|||
.with_style(style.container)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(f) => f(cx),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.collapsed()
|
||||
.contained()
|
||||
|
@ -302,6 +315,9 @@ impl ContextMenu {
|
|||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(_) => Empty::new().boxed(),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.collapsed()
|
||||
.constrained()
|
||||
|
@ -339,7 +355,7 @@ impl ContextMenu {
|
|||
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Label::new(label.to_string(), style.label.clone())
|
||||
Label::new(label.clone(), style.label.clone())
|
||||
.contained()
|
||||
.boxed(),
|
||||
)
|
||||
|
@ -366,6 +382,9 @@ impl ContextMenu {
|
|||
.on_drag(MouseButton::Left, |_, _| {})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
ContextMenuItem::Static(f) => f(cx),
|
||||
|
||||
ContextMenuItem::Separator => Empty::new()
|
||||
.constrained()
|
||||
.with_height(1.)
|
||||
|
|
|
@ -88,6 +88,7 @@ pub struct Titlebar {
|
|||
pub share_button: Interactive<ContainedText>,
|
||||
pub call_control: Interactive<IconButton>,
|
||||
pub toggle_contacts_button: Interactive<IconButton>,
|
||||
pub user_menu_button: Interactive<IconButton>,
|
||||
pub toggle_contacts_badge: ContainerStyle,
|
||||
}
|
||||
|
||||
|
|
|
@ -197,6 +197,11 @@ export default function workspace(colorScheme: ColorScheme) {
|
|||
color: foreground(layer, "variant", "hovered"),
|
||||
},
|
||||
},
|
||||
userMenuButton: {
|
||||
buttonWidth: 20,
|
||||
iconWidth: 12,
|
||||
...titlebarButton,
|
||||
},
|
||||
toggleContactsBadge: {
|
||||
cornerRadius: 3,
|
||||
padding: 2,
|
||||
|
|
Loading…
Reference in a new issue