diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 7b4b819b03..fbaf72b332 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -322,20 +322,7 @@ impl CollabTitlebarItem { ] }; - user_menu.show( - vec2f( - theme - .workspace - .titlebar - .user_menu_button - .default - .button_width, - theme.workspace.titlebar.height, - ), - AnchorCorner::TopRight, - items, - cx, - ); + user_menu.show(Default::default(), AnchorCorner::TopRight, items, cx); }); } @@ -402,7 +389,6 @@ impl CollabTitlebarItem { theme.tooltip.clone(), cx, ) - .aligned() .boxed(), ) .with_children(badge) @@ -547,10 +533,15 @@ impl CollabTitlebarItem { ) .contained() .with_margin_left(theme.workspace.titlebar.item_spacing) - .aligned() .boxed(), ) - .with_child(ChildView::new(&self.user_menu, cx).boxed()) + .with_child( + ChildView::new(&self.user_menu, cx) + .aligned() + .bottom() + .right() + .boxed(), + ) .boxed() } @@ -572,22 +563,18 @@ impl CollabTitlebarItem { fn render_contacts_popover_host<'a>( &'a self, - theme: &'a theme::Titlebar, + _theme: &'a theme::Titlebar, cx: &'a RenderContext, ) -> Option { 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() + Overlay::new(ChildView::new(popover, cx).boxed()) + .with_fit_mode(OverlayFitMode::SwitchAnchor) + .with_anchor_corner(AnchorCorner::TopRight) + .with_z_index(999) + .aligned() + .bottom() + .right() + .boxed() }) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index b0d7b78708..f4a5ad026a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -82,19 +82,13 @@ pub struct GoForward { } #[derive(Clone, PartialEq)] -pub struct DeploySplitMenu { - position: Vector2F, -} +pub struct DeploySplitMenu; #[derive(Clone, PartialEq)] -pub struct DeployDockMenu { - position: Vector2F, -} +pub struct DeployDockMenu; #[derive(Clone, PartialEq)] -pub struct DeployNewMenu { - position: Vector2F, -} +pub struct DeployNewMenu; impl_actions!(pane, [GoBack, GoForward, ActivateItem]); impl_internal_actions!( @@ -215,7 +209,7 @@ pub struct Pane { autoscroll: bool, nav_history: Rc>, toolbar: ViewHandle, - tab_bar_context_menu: ViewHandle, + tab_bar_context_menu: TabBarContextMenu, docked: Option, _background_actions: BackgroundActions, _workspace_id: usize, @@ -274,6 +268,27 @@ enum ItemType { All, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TabBarContextMenuKind { + New, + Split, + Dock, +} + +struct TabBarContextMenu { + kind: TabBarContextMenuKind, + handle: ViewHandle, +} + +impl TabBarContextMenu { + fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { + if self.kind == kind { + return Some(self.handle.clone()); + } + None + } +} + impl Pane { pub fn new( workspace_id: usize, @@ -283,6 +298,10 @@ impl Pane { ) -> Self { let handle = cx.weak_handle(); let context_menu = cx.add_view(ContextMenu::new); + context_menu.update(cx, |menu, _| { + menu.set_position_mode(OverlayPositionMode::Local) + }); + Self { items: Vec::new(), activation_history: Vec::new(), @@ -299,7 +318,10 @@ impl Pane { pane: handle.clone(), })), toolbar: cx.add_view(|_| Toolbar::new(handle)), - tab_bar_context_menu: context_menu, + tab_bar_context_menu: TabBarContextMenu { + kind: TabBarContextMenuKind::New, + handle: context_menu, + }, docked, _background_actions: background_actions, _workspace_id: workspace_id, @@ -1076,10 +1098,10 @@ impl Pane { cx.emit(Event::Split(direction)); } - fn deploy_split_menu(&mut self, action: &DeploySplitMenu, cx: &mut ViewContext) { - self.tab_bar_context_menu.update(cx, |menu, cx| { + fn deploy_split_menu(&mut self, _: &DeploySplitMenu, cx: &mut ViewContext) { + self.tab_bar_context_menu.handle.update(cx, |menu, cx| { menu.show( - action.position, + Default::default(), AnchorCorner::TopRight, vec![ ContextMenuItem::item("Split Right", SplitRight), @@ -1090,12 +1112,14 @@ impl Pane { cx, ); }); + + self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; } - fn deploy_dock_menu(&mut self, action: &DeployDockMenu, cx: &mut ViewContext) { - self.tab_bar_context_menu.update(cx, |menu, cx| { + fn deploy_dock_menu(&mut self, _: &DeployDockMenu, cx: &mut ViewContext) { + self.tab_bar_context_menu.handle.update(cx, |menu, cx| { menu.show( - action.position, + Default::default(), AnchorCorner::TopRight, vec![ ContextMenuItem::item("Anchor Dock Right", AnchorDockRight), @@ -1105,12 +1129,14 @@ impl Pane { cx, ); }); + + self.tab_bar_context_menu.kind = TabBarContextMenuKind::Dock; } - fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext) { - self.tab_bar_context_menu.update(cx, |menu, cx| { + fn deploy_new_menu(&mut self, _: &DeployNewMenu, cx: &mut ViewContext) { + self.tab_bar_context_menu.handle.update(cx, |menu, cx| { menu.show( - action.position, + Default::default(), AnchorCorner::TopRight, vec![ ContextMenuItem::item("New File", NewFile), @@ -1120,6 +1146,8 @@ impl Pane { cx, ); }); + + self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; } pub fn toolbar(&self) -> &ViewHandle { @@ -1398,28 +1426,45 @@ impl Pane { ) -> ElementBox { Flex::row() // New menu - .with_child(tab_bar_button(0, "icons/plus_12.svg", cx, |position| { - DeployNewMenu { position } - })) + .with_child(render_tab_bar_button( + 0, + "icons/plus_12.svg", + cx, + DeployNewMenu, + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::New), + )) .with_child( self.docked .map(|anchor| { // Add the dock menu button if this pane is a dock let dock_icon = icon_for_dock_anchor(anchor); - tab_bar_button(1, dock_icon, cx, |position| DeployDockMenu { position }) + render_tab_bar_button( + 1, + dock_icon, + cx, + DeployDockMenu, + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Dock), + ) }) .unwrap_or_else(|| { // Add the split menu if this pane is not a dock - tab_bar_button(2, "icons/split_12.svg", cx, |position| DeploySplitMenu { - position, - }) + render_tab_bar_button( + 2, + "icons/split_12.svg", + cx, + DeploySplitMenu, + self.tab_bar_context_menu + .handle_if_kind(TabBarContextMenuKind::Split), + ) }), ) // Add the close dock button if this pane is a dock .with_children( self.docked - .map(|_| tab_bar_button(3, "icons/x_mark_8.svg", cx, |_| HideDock)), + .map(|_| render_tab_bar_button(3, "icons/x_mark_8.svg", cx, HideDock, None)), ) .contained() .with_style(theme.workspace.tab_bar.pane_button_container) @@ -1554,7 +1599,6 @@ impl View for Pane { }) .boxed(), ) - .with_child(ChildView::new(&self.tab_bar_context_menu, cx).boxed()) .named("pane") } @@ -1575,7 +1619,7 @@ impl View for Pane { } cx.focus(active_item); - } else if focused != self.tab_bar_context_menu { + } else if focused != self.tab_bar_context_menu.handle { self.last_focused_view_by_item .insert(active_item.id(), focused.downgrade()); } @@ -1591,34 +1635,41 @@ impl View for Pane { } } -fn tab_bar_button( +fn render_tab_bar_button( index: usize, icon: &'static str, cx: &mut RenderContext, - action_builder: impl 'static + Fn(Vector2F) -> A, + action: A, + context_menu: Option>, ) -> ElementBox { enum TabBarButton {} - MouseEventHandler::::new(index, cx, |mouse_state, cx| { - let theme = &cx.global::().theme.workspace.tab_bar; - let style = theme.pane_button.style_for(mouse_state, false); - Svg::new(icon) - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - // .aligned() - .boxed() - }) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |e, cx| { - cx.dispatch_action(action_builder(e.region.lower_right())); - }) - .flex(1., false) - .boxed() + Stack::new() + .with_child( + MouseEventHandler::::new(index, cx, |mouse_state, cx| { + let theme = &cx.global::().theme.workspace.tab_bar; + let style = theme.pane_button.style_for(mouse_state, false); + Svg::new(icon) + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(action.clone()); + }) + .boxed(), + ) + .with_children( + context_menu.map(|menu| ChildView::new(menu, cx).aligned().bottom().right().boxed()), + ) + .flex(1., false) + .boxed() } impl ItemNavHistory { diff --git a/styles/src/styleTree/contactsPopover.ts b/styles/src/styleTree/contactsPopover.ts index 0819421daa..5e865aff17 100644 --- a/styles/src/styleTree/contactsPopover.ts +++ b/styles/src/styleTree/contactsPopover.ts @@ -8,7 +8,6 @@ export default function contactsPopover(colorScheme: ColorScheme) { background: background(layer), cornerRadius: 6, padding: { top: 6, bottom: 6 }, - margin: { top: -6 }, shadow: colorScheme.popoverShadow, border: border(layer), width: 300, diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 3569e82314..3d06a683ab 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -3,80 +3,82 @@ import { withOpacity } from "../utils/color" import { background, border, foreground, text } from "./components" export default function projectPanel(colorScheme: ColorScheme) { - let layer = colorScheme.middle + let layer = colorScheme.middle - let baseEntry = { - height: 24, - iconColor: foreground(layer, "variant"), - iconSize: 8, - iconSpacing: 8, - } + let baseEntry = { + height: 24, + iconColor: foreground(layer, "variant"), + iconSize: 8, + iconSpacing: 8, + } - let entry = { - ...baseEntry, - text: text(layer, "mono", "variant", { size: "sm" }), - hover: { - background: background(layer, "variant", "hovered"), - }, - active: { - background: colorScheme.isLight ? withOpacity(background(layer, "active"), 0.5) : background(layer, "active"), - text: text(layer, "mono", "active", { size: "sm" }), - }, - activeHover: { - background: background(layer, "active"), - text: text(layer, "mono", "active", { size: "sm" }), - }, - } + let entry = { + ...baseEntry, + text: text(layer, "mono", "variant", { size: "sm" }), + hover: { + background: background(layer, "variant", "hovered"), + }, + active: { + background: colorScheme.isLight + ? withOpacity(background(layer, "active"), 0.5) + : background(layer, "active"), + text: text(layer, "mono", "active", { size: "sm" }), + }, + activeHover: { + background: background(layer, "active"), + text: text(layer, "mono", "active", { size: "sm" }), + }, + } - return { - openProjectButton: { - background: background(layer), - border: border(layer, "active"), - cornerRadius: 4, - margin: { - top: 16, - left: 16, - right: 16, - }, - padding: { - top: 3, - bottom: 3, - left: 7, - right: 7, - }, - ...text(layer, "sans", "default", { size: "sm" }), - hover: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - }, - background: background(layer), - padding: { left: 12, right: 12, top: 6, bottom: 6 }, - indentWidth: 8, - entry, - draggedEntry: { - ...baseEntry, - text: text(layer, "mono", "on", { size: "sm" }), - background: withOpacity(background(layer, "on"), 0.9), - border: border(layer), - }, - ignoredEntry: { - ...entry, - text: text(layer, "mono", "disabled"), - }, - cutEntry: { - ...entry, - text: text(layer, "mono", "disabled"), - active: { - background: background(layer, "active"), - text: text(layer, "mono", "disabled", { size: "sm" }), - }, - }, - filenameEditor: { - background: background(layer, "on"), - text: text(layer, "mono", "on", { size: "sm" }), - selection: colorScheme.players[0], - }, - } + return { + openProjectButton: { + background: background(layer), + border: border(layer, "active"), + cornerRadius: 4, + margin: { + top: 16, + left: 16, + right: 16, + }, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + ...text(layer, "sans", "default", { size: "sm" }), + hover: { + ...text(layer, "sans", "default", { size: "sm" }), + background: background(layer, "hovered"), + border: border(layer, "active"), + }, + }, + background: background(layer), + padding: { left: 12, right: 12, top: 6, bottom: 6 }, + indentWidth: 8, + entry, + draggedEntry: { + ...baseEntry, + text: text(layer, "mono", "on", { size: "sm" }), + background: withOpacity(background(layer, "on"), 0.9), + border: border(layer), + }, + ignoredEntry: { + ...entry, + text: text(layer, "mono", "disabled"), + }, + cutEntry: { + ...entry, + text: text(layer, "mono", "disabled"), + active: { + background: background(layer, "active"), + text: text(layer, "mono", "disabled", { size: "sm" }), + }, + }, + filenameEditor: { + background: background(layer, "on"), + text: text(layer, "mono", "on", { size: "sm" }), + selection: colorScheme.players[0], + }, + } }