From f403d87eff0f96ca4d05664816ba3ca9354ff447 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 May 2022 10:23:43 +0200 Subject: [PATCH] WIP --- Cargo.lock | 2 + crates/collab/src/main.rs | 1 + crates/context_menu/Cargo.toml | 1 + crates/context_menu/src/context_menu.rs | 115 ++++++++++++++++++++-- crates/project_panel/Cargo.toml | 1 + crates/project_panel/src/project_panel.rs | 55 +++++------ crates/theme/src/theme.rs | 3 +- styles/src/styleTree/projectPanel.ts | 4 +- 8 files changed, 140 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0233d6827363b1bafae9e1f9f3c404e1900c55f..e8b571add83f19c7e8437c66059c98f516c2dfc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,6 +979,7 @@ name = "context_menu" version = "0.1.0" dependencies = [ "gpui", + "settings", "theme", ] @@ -3457,6 +3458,7 @@ dependencies = [ name = "project_panel" version = "0.1.0" dependencies = [ + "context_menu", "editor", "futures", "gpui", diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index 74401699ca62afbc23273f667494258d955af9e9..784987534da5deed1cf21c2355e6a19c8f773b8b 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -149,6 +149,7 @@ pub fn init_tracing(config: &Config) -> Option<()> { use tracing_subscriber::layer::SubscriberExt; let rust_log = config.rust_log.clone()?; + println!("HEY!"); LogTracer::init().log_err()?; let open_telemetry_layer = config diff --git a/crates/context_menu/Cargo.toml b/crates/context_menu/Cargo.toml index 3392d68579b788c41eb32775f3035853144da138..c33b935c45327a87d9917f89c4c0cec8ac9ffaa7 100644 --- a/crates/context_menu/Cargo.toml +++ b/crates/context_menu/Cargo.toml @@ -9,4 +9,5 @@ doctest = false [dependencies] gpui = { path = "../gpui" } +settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 8d698ce9be56b67f5a7bc89da283dac207ae792a..c3f00ac5b6fea33a1a0dcb9ddc9db487ae59fd9c 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -1,6 +1,10 @@ -use gpui::{Entity, View}; +use gpui::{ + elements::*, geometry::vector::Vector2F, Action, Entity, RenderContext, View, ViewContext, +}; +use settings::Settings; +use std::{marker::PhantomData, sync::Arc}; -enum ContextMenuItem { +pub enum ContextMenuItem { Item { label: String, action: Box, @@ -8,21 +12,116 @@ enum ContextMenuItem { Separator, } -pub struct ContextMenu { +pub struct ContextMenu { position: Vector2F, - items: Vec, + items: Arc<[ContextMenuItem]>, + state: UniformListState, + selected_index: Option, + widest_item_index: Option, + visible: bool, + _phantom: PhantomData, } -impl Entity for ContextMenu { +impl Entity for ContextMenu { type Event = (); } -impl View for ContextMenu { +impl View for ContextMenu { fn ui_name() -> &'static str { "ContextMenu" } - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { - Overlay::new().with_abs_position(self.position).boxed() + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + if !self.visible { + return Empty::new().boxed(); + } + + let theme = &cx.global::().theme; + let menu_style = &theme.project_panel.context_menu; + let separator_style = menu_style.separator; + let item_style = menu_style.item.clone(); + let items = self.items.clone(); + let selected_ix = self.selected_index; + Overlay::new( + UniformList::new( + self.state.clone(), + self.items.len(), + move |range, elements, cx| { + let start = range.start; + elements.extend(items[range].iter().enumerate().map(|(ix, item)| { + let item_ix = start + ix; + match item { + ContextMenuItem::Item { label, action } => { + let action = action.boxed_clone(); + MouseEventHandler::new::(item_ix, cx, |state, _| { + let style = + item_style.style_for(state, Some(item_ix) == selected_ix); + Flex::row() + .with_child( + Label::new(label.to_string(), style.label.clone()) + .boxed(), + ) + .boxed() + }) + .on_click(move |_, _, cx| { + cx.dispatch_any_action(action.boxed_clone()) + }) + .boxed() + } + ContextMenuItem::Separator => { + Empty::new().contained().with_style(separator_style).boxed() + } + } + })) + }, + ) + .with_width_from_item(self.widest_item_index) + .boxed(), + ) + .with_abs_position(self.position) + .contained() + .with_style(menu_style.container) + .boxed() + } + + fn on_blur(&mut self, cx: &mut ViewContext) { + self.visible = false; + cx.notify(); + } +} + +impl ContextMenu { + pub fn new() -> Self { + Self { + position: Default::default(), + items: Arc::from([]), + state: Default::default(), + selected_index: Default::default(), + widest_item_index: Default::default(), + visible: false, + _phantom: PhantomData, + } + } + + pub fn show( + &mut self, + position: Vector2F, + items: impl IntoIterator, + cx: &mut ViewContext, + ) { + self.items = items.into_iter().collect(); + self.widest_item_index = self + .items + .iter() + .enumerate() + .max_by_key(|(_, item)| match item { + ContextMenuItem::Item { label, .. } => label.chars().count(), + ContextMenuItem::Separator => 0, + }) + .map(|(ix, _)| ix); + self.position = position; + self.visible = true; + cx.focus_self(); + cx.notify(); } } diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 257bac21d9c2c43f05378dd3556d0808a63289fc..7eb0282660883bdcf58aeea38f1350ed66f7ff19 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -8,6 +8,7 @@ path = "src/project_panel.rs" doctest = false [dependencies] +context_menu = { path = "../context_menu" } editor = { path = "../editor" } gpui = { path = "../gpui" } project = { path = "../project" } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d3d4856f4d35fece2df1faba6cca3f19c68c6e95..85d1ed7407d88a51748f99acfd6bec574222e42c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,17 +1,18 @@ +use context_menu::{ContextMenu, ContextMenuItem}; use editor::{Cancel, Editor}; use futures::stream::StreamExt; use gpui::{ actions, anyhow::{anyhow, Result}, elements::{ - ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, Overlay, ParentElement, + ChildView, ConstrainedBox, Empty, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState, }, geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle, - AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, - RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use settings::Settings; @@ -37,7 +38,7 @@ pub struct ProjectPanel { selection: Option, edit_state: Option, filename_editor: ViewHandle, - context_menu: Option, + context_menu: ViewHandle>, handle: WeakViewHandle, } @@ -83,11 +84,6 @@ pub struct DeployContextMenu { pub entry_id: Option, } -pub struct ContextMenu { - pub position: Vector2F, - pub entry_id: Option, -} - actions!( project_panel, [ @@ -170,7 +166,7 @@ impl ProjectPanel { selection: None, edit_state: None, filename_editor, - context_menu: None, + context_menu: cx.add_view(|_| ContextMenu::new()), handle: cx.weak_handle(), }; this.update_visible_entries(None, cx); @@ -211,9 +207,22 @@ impl ProjectPanel { } fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext) { - self.context_menu = Some(ContextMenu { - position: action.position, - entry_id: action.entry_id, + self.context_menu.update(cx, |menu, cx| { + menu.show( + action.position, + [ + ContextMenuItem::Item { + label: "New File".to_string(), + action: Box::new(AddFile), + }, + ContextMenuItem::Item { + label: "New Directory".to_string(), + action: Box::new(AddDirectory), + }, + ContextMenuItem::Separator, + ], + cx, + ); }); cx.notify(); } @@ -883,24 +892,6 @@ impl ProjectPanel { .with_cursor_style(CursorStyle::PointingHand) .boxed() } - - fn render_context_menu(&self, cx: &mut RenderContext) -> Option { - self.context_menu.as_ref().map(|menu| { - let style = &cx.global::().theme.project_panel.context_menu; - - Overlay::new( - Flex::column() - .with_child( - Label::new("Add File".to_string(), style.item.label.clone()).boxed(), - ) - .contained() - .with_style(style.container) - .boxed(), - ) - .with_abs_position(menu.position) - .named("Project Panel Context Menu") - }) - } } impl View for ProjectPanel { @@ -943,7 +934,7 @@ impl View for ProjectPanel { .with_style(container_style) .boxed(), ) - .with_children(self.render_context_menu(cx)) + .with_child(ChildView::new(&self.context_menu).boxed()) .boxed() } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c69a8d389831b50591cc5b7257604f36f33b79b5..941ae03d4d87bc45494c8d7b00832f03c7a87269 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -244,7 +244,8 @@ pub struct ProjectPanelEntry { pub struct ContextMenu { #[serde(flatten)] pub container: ContainerStyle, - pub item: ContextMenuItem, + pub item: Interactive, + pub separator: ContainerStyle, } #[derive(Clone, Debug, Deserialize, Default)] diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts index 66aaa85952ce26010e76c9a8d0721c7fcfaaf51e..b2d8b9d4acd613088eaefbbba37725e21ecd36d1 100644 --- a/styles/src/styleTree/projectPanel.ts +++ b/styles/src/styleTree/projectPanel.ts @@ -43,7 +43,9 @@ export default function projectPanel(theme: Theme) { right: 6, top: 2, }, - label: text(theme, "sans", "secondary", { size: "sm" }), + item: { + label: text(theme, "sans", "secondary", { size: "sm" }), + }, shadow: shadow(theme), } };