From 0f0b7090b8f6735248837691b58642d8328c8526 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 16 Nov 2023 13:10:43 +0200 Subject: [PATCH 1/3] Fix more errors in terminal element --- crates/terminal_view2/src/terminal_element.rs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 00f1dca17d686766310fb1fe67c2704b9c736eaf..bc9ca5d0e82f472ea2ad637f530b6f246cba3340 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,8 +1,8 @@ // use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; // use gpui::{ -// AnyElement, AppContext, Bounds, Component, Element, HighlightStyle, Hsla, LayoutId, Line, -// ModelContext, MouseButton, Pixels, Point, TextStyle, Underline, ViewContext, WeakModel, -// WindowContext, +// point, transparent_black, AnyElement, AppContext, Bounds, Component, CursorStyle, Element, +// FontStyle, FontWeight, HighlightStyle, Hsla, LayoutId, Line, ModelContext, MouseButton, +// Overlay, Pixels, Point, Quad, TextStyle, Underline, ViewContext, WeakModel, WindowContext, // }; // use itertools::Itertools; // use language::CursorShape; @@ -130,23 +130,24 @@ // cx: &mut ViewContext, // ) { // let position = { -// let point = self.point; -// vec2f( -// (origin.x() + point.column as f32 * layout.size.cell_width).floor(), -// origin.y() + point.line as f32 * layout.size.line_height, +// let alac_point = self.point; +// point( +// (origin.x + alac_point.column as f32 * layout.size.cell_width).floor(), +// origin.y + alac_point.line as f32 * layout.size.line_height, // ) // }; -// let size = vec2f( +// let size = point( // (layout.size.cell_width * self.num_of_cells as f32).ceil(), // layout.size.line_height, -// ); +// ) +// .into(); // cx.paint_quad( // Bounds::new(position, size), // Default::default(), // self.color, // Default::default(), -// Default::default(), +// transparent_black(), // ); // } // } @@ -281,9 +282,9 @@ // cursor_point: DisplayCursor, // size: TerminalSize, // text_fragment: &Line, -// ) -> Option<(Vector2F, f32)> { +// ) -> Option<(Point, Pixels)> { // if cursor_point.line() < size.total_lines() as i32 { -// let cursor_width = if text_fragment.width == 0. { +// let cursor_width = if text_fragment.width == Pixels::ZERO { // size.cell_width() // } else { // text_fragment.width @@ -292,7 +293,7 @@ // //Cursor should always surround as much of the text as possible, // //hence when on pixel boundaries round the origin down and the width up // Some(( -// vec2f( +// point( // (cursor_point.col() as f32 * size.cell_width()).floor(), // (cursor_point.line() as f32 * size.line_height()).floor(), // ), @@ -332,15 +333,15 @@ // let mut properties = Properties::new(); // if indexed.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { -// properties = *properties.weight(Weight::BOLD); +// properties = *properties.weight(FontWeight::BOLD); // } // if indexed.flags.intersects(Flags::ITALIC) { -// properties = *properties.style(Italic); +// properties = *properties.style(FontStyle::Italic); // } // let font_id = font_cache -// .select_font(text_style.font_family_id, &properties) -// .unwrap_or(8text_style.font_id); +// .select_font(text_style.font_family, &properties) +// .unwrap_or(text_style.font_id); // let mut result = RunStyle { // color: fg, @@ -366,7 +367,7 @@ // fn generic_button_handler( // connection: WeakModel, // origin: Point, -// f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext), +// f: impl Fn(&mut Terminal, Point, E, &mut ModelContext), // ) -> impl Fn(E, &mut TerminalView, &mut EventContext) { // move |event, _: &mut TerminalView, cx| { // cx.focus_parent(); @@ -522,9 +523,9 @@ // fn layout( // &mut self, // view_state: &mut TerminalView, -// element_state: &mut Self::ElementState, +// element_state: Option, // cx: &mut ViewContext, -// ) -> LayoutId { +// ) -> (LayoutId, Self::ElementState) { // let settings = ThemeSettings::get_global(cx); // let terminal_settings = TerminalSettings::get_global(cx); @@ -569,7 +570,7 @@ // let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size); // gutter = cell_width; -// let size = constraint.max - vec2f(gutter, 0.); +// let size = constraint.max - point(gutter, 0.); // TerminalSize::new(line_height, cell_width, size) // }; @@ -607,11 +608,11 @@ // cx, // ), // ) -// .with_position_mode(gpui::elements::OverlayPositionMode::Local) +// .with_position_mode(gpui::OverlayPositionMode::Local) // .into_any(); // tooltip.layout( -// SizeConstraint::new(Vector2F::zero(), cx.window_size()), +// SizeConstraint::new(Point::zero(), cx.window_size()), // view_state, // cx, // ); @@ -735,7 +736,7 @@ // let clip_bounds = Some(visible_bounds); // cx.paint_layer(clip_bounds, |cx| { -// let origin = bounds.origin() + vec2f(element_state.gutter, 0.); +// let origin = bounds.origin + point(element_state.gutter, 0.); // // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse // self.attach_mouse_handlers(origin, visible_bounds, element_state.mode, cx); @@ -808,7 +809,7 @@ // }); // } -// fn id(&self) -> Option { +// fn element_id(&self) -> Option { // todo!() // } @@ -842,12 +843,12 @@ // // ) -> Option> { // // // Use the same origin that's passed to `Cursor::paint` in the paint // // // method bove. -// // let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); +// // let mut origin = bounds.origin() + point(layout.size.cell_width, 0.); // // // TODO - Why is it necessary to move downward one line to get correct // // // positioning? I would think that we'd want the same rect that is // // // painted for the cursor. -// // origin += vec2f(0., layout.size.line_height); +// // origin += point(0., layout.size.line_height); // // Some(layout.cursor.as_ref()?.bounding_rect(origin)) // // } @@ -886,7 +887,7 @@ // range: &RangeInclusive, // layout: &LayoutState, // origin: Point, -// ) -> Option<(f32, Vec)> { +// ) -> Option<(Pixels, Vec)> { // // Step 1. Normalize the points to be viewport relative. // // When display_offset = 1, here's how the grid is arranged: // //-2,0 -2,1... @@ -937,8 +938,8 @@ // } // highlighted_range_lines.push(HighlightedRangeLine { -// start_x: origin.x() + line_start as f32 * layout.size.cell_width, -// end_x: origin.x() + line_end as f32 * layout.size.cell_width, +// start_x: origin.x + line_start as f32 * layout.size.cell_width, +// end_x: origin.x + line_end as f32 * layout.size.cell_width, // }); // } From f638d4ce1d81ce4a0722091a9b64e06a19160b7c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 16 Nov 2023 14:32:37 +0200 Subject: [PATCH 2/3] Add basic context menu element --- Cargo.lock | 15 + Cargo.toml | 1 + crates/context_menu2/Cargo.toml | 19 + crates/context_menu2/src/context_menu.rs | 557 ++++++++++++++++++ crates/terminal_view2/Cargo.toml | 2 +- crates/terminal_view2/src/terminal_element.rs | 3 +- crates/terminal_view2/src/terminal_view.rs | 67 ++- crates/zed2/Cargo.toml | 2 +- crates/zed2/src/main.rs | 2 +- 9 files changed, 633 insertions(+), 35 deletions(-) create mode 100644 crates/context_menu2/Cargo.toml create mode 100644 crates/context_menu2/src/context_menu.rs diff --git a/Cargo.lock b/Cargo.lock index 4bbada23d044759885088153dfbc3746130de4b2..87c370f59e82cfa36f8452ca4477453739ea1391 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1989,6 +1989,19 @@ dependencies = [ "theme", ] +[[package]] +name = "context_menu2" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui2", + "menu2", + "settings2", + "smallvec", + "theme2", + "workspace2", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -9181,6 +9194,7 @@ version = "0.1.0" dependencies = [ "anyhow", "client2", + "context_menu2", "db2", "dirs 4.0.0", "editor2", @@ -11518,6 +11532,7 @@ dependencies = [ "collab_ui2", "collections", "command_palette2", + "context_menu2", "copilot2", "ctor", "db2", diff --git a/Cargo.toml b/Cargo.toml index f8d0af77fa85220f348c866edfe5242d0cccbeec..65c7630077e93cf8c23158ae175b359fdba3b7f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "crates/command_palette2", "crates/component_test", "crates/context_menu", + "crates/context_menu2", "crates/copilot", "crates/copilot2", "crates/copilot_button", diff --git a/crates/context_menu2/Cargo.toml b/crates/context_menu2/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5c8aae03a1007d85c32df61ad64f5fcb0b5aca49 --- /dev/null +++ b/crates/context_menu2/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "context_menu2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/context_menu.rs" +doctest = false + +[dependencies] +gpui = { package = "gpui2", path = "../gpui2" } +menu = { package = "menu2", path = "../menu2" } +settings = { package = "settings2", path = "../settings2" } +theme = { package = "theme2", path = "../theme2" } +workspace = { package = "workspace2", path = "../workspace2" } + +anyhow.workspace = true +smallvec.workspace = true diff --git a/crates/context_menu2/src/context_menu.rs b/crates/context_menu2/src/context_menu.rs new file mode 100644 index 0000000000000000000000000000000000000000..885c5c8521a91e1a4a44ed362b8d498970fa1481 --- /dev/null +++ b/crates/context_menu2/src/context_menu.rs @@ -0,0 +1,557 @@ +#![allow(unused_variables, unused)] +//todo!(remove) + +use gpui::{ + div, Action, AnchorCorner, AnyElement, AppContext, BorrowWindow, Div, EntityId, FocusHandle, + FocusableView, Pixels, Point, Render, ViewContext, +}; +use menu::*; + +use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration}; + +pub fn init(cx: &mut AppContext) { + // todo!() + // cx.observe_new_views( + // |workspace: &mut Workspace, _: &mut ViewContext| { + // workspace.register_action(ContextMenu::select_first); + // workspace.register_action(ContextMenu::select_last); + // workspace.register_action(ContextMenu::select_next); + // workspace.register_action(ContextMenu::select_prev); + // workspace.register_action(ContextMenu::confirm); + // workspace.register_action(ContextMenu::cancel); + // }, + // ) + // .detach(); +} + +pub type StaticItem = Box AnyElement>; + +type ContextMenuItemBuilder = (); +// todo!() +// Box AnyElement>; + +pub enum ContextMenuItemLabel { + String(Cow<'static, str>), + Element(ContextMenuItemBuilder), +} + +impl From> for ContextMenuItemLabel { + fn from(s: Cow<'static, str>) -> Self { + Self::String(s) + } +} + +impl From<&'static str> for ContextMenuItemLabel { + fn from(s: &'static str) -> Self { + Self::String(s.into()) + } +} + +impl From for ContextMenuItemLabel { + fn from(s: String) -> Self { + Self::String(s.into()) + } +} + +// todo!() +// impl From for ContextMenuItemLabel +// where +// T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement, +// { +// fn from(f: T) -> Self { +// Self::Element(Box::new(f)) +// } +// } + +pub enum ContextMenuItemAction { + Action(Box), + Handler(Arc)>), +} + +impl Clone for ContextMenuItemAction { + fn clone(&self) -> Self { + match self { + Self::Action(action) => Self::Action(action.boxed_clone()), + Self::Handler(handler) => Self::Handler(handler.clone()), + } + } +} + +pub enum ContextMenuItem { + Item { + label: ContextMenuItemLabel, + action: ContextMenuItemAction, + }, + Static(StaticItem), + Separator, +} + +impl ContextMenuItem { + pub fn action(label: impl Into, action: impl 'static + Action) -> Self { + Self::Item { + label: label.into(), + action: ContextMenuItemAction::Action(Box::new(action)), + } + } + + pub fn handler( + label: impl Into, + handler: impl 'static + Fn(&mut ViewContext), + ) -> Self { + Self::Item { + label: label.into(), + action: ContextMenuItemAction::Handler(Arc::new(handler)), + } + } + + pub fn separator() -> Self { + Self::Separator + } + + fn is_action(&self) -> bool { + matches!(self, Self::Item { .. }) + } + + fn action_id(&self) -> Option { + match self { + ContextMenuItem::Item { action, .. } => match action { + ContextMenuItemAction::Action(action) => Some(action.type_id()), + ContextMenuItemAction::Handler(_) => None, + }, + ContextMenuItem::Static(..) | ContextMenuItem::Separator => None, + } + } +} + +pub struct ContextMenu { + show_count: usize, + anchor_position: Point, + anchor_corner: AnchorCorner, + // todo!() + // position_mode: OverlayPositionMode, + items: Vec, + selected_index: Option, + visible: bool, + delay_cancel: bool, + previously_focused_view_handle: Option, + parent_view_id: EntityId, + focus_handle: FocusHandle, + // todo!() + // _actions_observation: Subscription, +} + +impl FocusableView for ContextMenu { + fn focus_handle(&self, _: &AppContext) -> FocusHandle { + self.focus_handle.clone() + } +} + +// todo!() +// fn ui_name() -> &'static str { +// "ContextMenu" +// } +// +// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { +// Self::reset_to_default_keymap_context(keymap); +// keymap.add_identifier("menu"); +// } +// +// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext) { +// self.reset(cx); +// } + +impl Render for ContextMenu { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + if !self.visible { + return div(); + } + + // todo!() + // // Render the menu once at minimum width. + // let mut collapsed_menu = self.render_menu_for_measurement(cx); + // let expanded_menu = self + // .render_menu(cx) + // .dynamically(move |constraint, view, cx| { + // SizeConstraint::strict_along( + // Axis::Horizontal, + // collapsed_menu.layout(constraint, view, cx).0.x(), + // ) + // }); + + // Overlay::new(expanded_menu) + // .with_hoverable(true) + // .with_fit_mode(OverlayFitMode::SnapToWindow) + // .with_anchor_position(self.anchor_position) + // .with_anchor_corner(self.anchor_corner) + // .with_position_mode(self.position_mode) + // .into_any() + div() + } +} + +impl ContextMenu { + pub fn new(parent_view_id: EntityId, cx: &mut ViewContext) -> Self { + Self { + show_count: 0, + delay_cancel: false, + anchor_position: Default::default(), + anchor_corner: AnchorCorner::TopLeft, + // todo!() + // position_mode: OverlayPositionMode::Window, + items: Default::default(), + selected_index: Default::default(), + visible: Default::default(), + previously_focused_view_handle: None, + parent_view_id, + // todo!() + // _actions_observation: cx.observe_actions(Self::action_dispatched), + focus_handle: cx.focus_handle(), + } + } + + pub fn visible(&self) -> bool { + self.visible + } + + fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext) { + if let Some(ix) = self + .items + .iter() + .position(|item| item.action_id() == Some(action_id)) + { + self.selected_index = Some(ix); + cx.notify(); + cx.spawn(|this, mut cx| async move { + cx.background_executor() + .timer(Duration::from_millis(50)) + .await; + this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } + } + + fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { + if let Some(ix) = self.selected_index { + if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) { + match action { + ContextMenuItemAction::Action(action) => { + let window = cx.window(); + let view_id = self.parent_view_id; + let action = action.boxed_clone(); + // todo!() + // cx.app_context() + // .spawn(|mut cx| async move { + // window + // .dispatch_action(view_id, action.as_ref(), &mut cx) + // .ok_or_else(|| anyhow!("window was closed")) + // }) + // .detach_and_log_err(cx); + } + ContextMenuItemAction::Handler(handler) => handler(cx), + } + self.reset(cx); + } + } + } + + pub fn delay_cancel(&mut self) { + self.delay_cancel = true; + } + + fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { + if !self.delay_cancel { + self.reset(cx); + let show_count = self.show_count; + cx.defer(move |this, cx| { + if this.focus_handle.is_focused(cx) && this.show_count == show_count { + if let Some(previously_focused_view_handle) = + this.previously_focused_view_handle.take() + { + previously_focused_view_handle.focus(cx); + } + } + }); + } else { + self.delay_cancel = false; + } + } + + fn reset(&mut self, cx: &mut ViewContext) { + self.items.clear(); + self.visible = false; + self.selected_index.take(); + cx.notify(); + } + + fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { + self.selected_index = self.items.iter().position(|item| item.is_action()); + cx.notify(); + } + + fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { + for (ix, item) in self.items.iter().enumerate().rev() { + if item.is_action() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } + + fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { + if let Some(ix) = self.selected_index { + for (ix, item) in self.items.iter().enumerate().skip(ix + 1) { + if item.is_action() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } else { + self.select_first(&Default::default(), cx); + } + } + + fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { + if let Some(ix) = self.selected_index { + for (ix, item) in self.items.iter().enumerate().take(ix).rev() { + if item.is_action() { + self.selected_index = Some(ix); + cx.notify(); + break; + } + } + } else { + self.select_last(&Default::default(), cx); + } + } + + pub fn toggle( + &mut self, + anchor_position: Point, + anchor_corner: AnchorCorner, + items: Vec, + cx: &mut ViewContext, + ) { + if self.visible() { + self.cancel(&Cancel, cx); + } else { + let mut items = items.into_iter().peekable(); + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !self.focus_handle.is_focused(cx) { + self.previously_focused_view_handle = cx.focused(); + } + cx.focus_self(); + } else { + self.visible = false; + } + } + cx.notify(); + } + + pub fn show( + &mut self, + anchor_position: Point, + anchor_corner: AnchorCorner, + items: Vec, + cx: &mut ViewContext, + ) { + let mut items = items.into_iter().peekable(); + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !self.focus_handle.is_focused(cx) { + self.previously_focused_view_handle = cx.focused(); + } + cx.focus_self(); + } else { + self.visible = false; + } + cx.notify(); + } + + // todo!() + // pub fn set_position_mode(&mut self, mode: OverlayPositionMode) { + // self.position_mode = mode; + // } + + fn render_menu_for_measurement(&self, cx: &mut ViewContext) -> Div { + // let style = theme::current(cx).context_menu.clone(); + // Flex::row() + // .with_child( + // Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| { + // match item { + // ContextMenuItem::Item { label, .. } => { + // let style = style.item.in_state(self.selected_index == Some(ix)); + // let style = style.style_for(&mut Default::default()); + + // match label { + // ContextMenuItemLabel::String(label) => { + // Label::new(label.to_string(), style.label.clone()) + // .contained() + // .with_style(style.container) + // .into_any() + // } + // ContextMenuItemLabel::Element(element) => { + // element(&mut Default::default(), style) + // } + // } + // } + + // ContextMenuItem::Static(f) => f(cx), + + // ContextMenuItem::Separator => Empty::new() + // .collapsed() + // .contained() + // .with_style(style.separator) + // .constrained() + // .with_height(1.) + // .into_any(), + // } + // })), + // ) + // .with_child( + // Flex::column() + // .with_children(self.items.iter().enumerate().map(|(ix, item)| { + // match item { + // ContextMenuItem::Item { action, .. } => { + // let style = style.item.in_state(self.selected_index == Some(ix)); + // let style = style.style_for(&mut Default::default()); + + // match action { + // ContextMenuItemAction::Action(action) => KeystrokeLabel::new( + // self.parent_view_id, + // action.boxed_clone(), + // style.keystroke.container, + // style.keystroke.text.clone(), + // ) + // .into_any(), + // ContextMenuItemAction::Handler(_) => Empty::new().into_any(), + // } + // } + + // ContextMenuItem::Static(_) => Empty::new().into_any(), + + // ContextMenuItem::Separator => Empty::new() + // .collapsed() + // .constrained() + // .with_height(1.) + // .contained() + // .with_style(style.separator) + // .into_any(), + // } + // })) + // .contained() + // .with_margin_left(style.keystroke_margin), + // ) + // .contained() + // .with_style(style.container) + todo!() + } + + fn render_menu(&self, cx: &mut ViewContext) -> Div { + enum Menu {} + enum MenuItem {} + + // let style = theme::current(cx).context_menu.clone(); + + // MouseEventHandler::new::(0, cx, |_, cx| { + // Flex::column() + // .with_children(self.items.iter().enumerate().map(|(ix, item)| { + // match item { + // ContextMenuItem::Item { label, action } => { + // let action = action.clone(); + // let view_id = self.parent_view_id; + // MouseEventHandler::new::(ix, cx, |state, _| { + // let style = style.item.in_state(self.selected_index == Some(ix)); + // let style = style.style_for(state); + // let keystroke = match &action { + // ContextMenuItemAction::Action(action) => Some( + // KeystrokeLabel::new( + // view_id, + // action.boxed_clone(), + // style.keystroke.container, + // style.keystroke.text.clone(), + // ) + // .flex_float(), + // ), + // ContextMenuItemAction::Handler(_) => None, + // }; + + // Flex::row() + // .with_child(match label { + // ContextMenuItemLabel::String(label) => { + // Label::new(label.clone(), style.label.clone()) + // .contained() + // .into_any() + // } + // ContextMenuItemLabel::Element(element) => { + // element(state, style) + // } + // }) + // .with_children(keystroke) + // .contained() + // .with_style(style.container) + // }) + // .with_cursor_style(CursorStyle::PointingHand) + // .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events + // .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events + // .on_click(MouseButton::Left, move |_, menu, cx| { + // menu.cancel(&Default::default(), cx); + // let window = cx.window(); + // match &action { + // ContextMenuItemAction::Action(action) => { + // let action = action.boxed_clone(); + // cx.app_context() + // .spawn(|mut cx| async move { + // window + // .dispatch_action( + // view_id, + // action.as_ref(), + // &mut cx, + // ) + // .ok_or_else(|| anyhow!("window was closed")) + // }) + // .detach_and_log_err(cx); + // } + // ContextMenuItemAction::Handler(handler) => handler(cx), + // } + // }) + // .on_drag(MouseButton::Left, |_, _, _| {}) + // .into_any() + // } + + // ContextMenuItem::Static(f) => f(cx), + + // ContextMenuItem::Separator => Empty::new() + // .constrained() + // .with_height(1.) + // .contained() + // .with_style(style.separator) + // .into_any(), + // } + // })) + // .contained() + // .with_style(style.container) + // }) + // .on_down_out(MouseButton::Left, |_, this, cx| { + // this.cancel(&Default::default(), cx); + // }) + // .on_down_out(MouseButton::Right, |_, this, cx| { + // this.cancel(&Default::default(), cx); + // }) + todo!() + } +} diff --git a/crates/terminal_view2/Cargo.toml b/crates/terminal_view2/Cargo.toml index f0d2e6ccf02f2d5aa9673136b2cba6f4cf283322..714ccb60a2ca3cfb2bdc651de2991dbd8d5a6486 100644 --- a/crates/terminal_view2/Cargo.toml +++ b/crates/terminal_view2/Cargo.toml @@ -9,7 +9,7 @@ path = "src/terminal_view.rs" doctest = false [dependencies] -# context_menu = { package = "context_menu2", path = "../context_menu2" } +context_menu = { package = "context_menu2", path = "../context_menu2" } editor = { package = "editor2", path = "../editor2" } language = { package = "language2", path = "../language2" } gpui = { package = "gpui2", path = "../gpui2" } diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index bc9ca5d0e82f472ea2ad637f530b6f246cba3340..e93d82047d6782f518db309ac3c132d642dd2d46 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -23,6 +23,7 @@ // TerminalSize, // }; // use theme::ThemeSettings; +// use workspace::ElementId; // use std::mem; // use std::{fmt::Debug, ops::RangeInclusive}; @@ -809,7 +810,7 @@ // }); // } -// fn element_id(&self) -> Option { +// fn element_id(&self) -> Option { // todo!() // } diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 55b7c1af669c0c7039f664bf23b2a93c520009a4..ad47e720e25f6d0fbd3b7fba5f58c4bffc0faea7 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -7,27 +7,18 @@ pub mod terminal_panel; // todo!() // use crate::terminal_element::TerminalElement; -use anyhow::Context; -use dirs::home_dir; +use context_menu::{ContextMenu, ContextMenuItem}; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, img, red, register_action, AnyElement, AppContext, Component, DispatchPhase, Div, - EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, - InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, ParentComponent, Pixels, - Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, + actions, div, img, red, register_action, AnchorCorner, AnyElement, AppContext, Component, + DispatchPhase, Div, EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, + FocusableView, InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, + ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, + WeakView, }; use language::Bias; use persistence::TERMINAL_DB; use project::{search::SearchQuery, LocalWorktree, Project}; -use serde::Deserialize; -use settings::Settings; -use smol::Timer; -use std::{ - ops::RangeInclusive, - path::{Path, PathBuf}, - sync::Arc, - time::Duration, -}; use terminal::{ alacritty_terminal::{ index::Point, @@ -42,7 +33,20 @@ use workspace::{ notifications::NotifyResultExt, register_deserializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem}, - NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, + CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, +}; + +use anyhow::Context; +use dirs::home_dir; +use serde::Deserialize; +use settings::Settings; +use smol::Timer; + +use std::{ + ops::RangeInclusive, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, }; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -82,7 +86,7 @@ pub struct TerminalView { has_new_content: bool, //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, - // context_menu: View, + context_menu: View, blink_state: bool, blinking_on: bool, blinking_paused: bool, @@ -265,8 +269,7 @@ impl TerminalView { has_new_content: true, has_bell: false, focus_handle: cx.focus_handle(), - // todo!() - // context_menu: cx.build_view(|cx| ContextMenu::new(view_id, cx)), + context_menu: cx.build_view(|cx| ContextMenu::new(view_id, cx)), blink_state: true, blinking_on: false, blinking_paused: false, @@ -293,18 +296,21 @@ impl TerminalView { cx.emit(Event::Wakeup); } - pub fn deploy_context_menu(&mut self, _position: Point, _cx: &mut ViewContext) { - //todo!(context_menu) - // let menu_entries = vec![ - // ContextMenuItem::action("Clear", Clear), - // ContextMenuItem::action("Close", pane::CloseActiveItem { save_intent: None }), - // ]; + pub fn deploy_context_menu( + &mut self, + position: gpui::Point, + cx: &mut ViewContext, + ) { + let menu_entries = vec![ + ContextMenuItem::action("Clear", Clear), + ContextMenuItem::action("Close", CloseActiveItem { save_intent: None }), + ]; - // self.context_menu.update(cx, |menu, cx| { - // menu.show(position, AnchorCorner::TopLeft, menu_entries, cx) - // }); + self.context_menu.update(cx, |menu, cx| { + menu.show(position, AnchorCorner::TopLeft, menu_entries, cx) + }); - // cx.notify(); + cx.notify(); } fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { @@ -561,8 +567,7 @@ impl Render for TerminalView { // self.can_navigate_to_selected_word, // ) ) - // todo!() - // .child(ChildView::new(&self.context_menu, cx)) + .child(self.context_menu.clone()) } } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index f471ea230656bc116aab196a0e21c4a2d54d47e0..398a7a8920562c27e1093df31dab4c90211661f6 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -27,7 +27,7 @@ collab_ui = { package = "collab_ui2", path = "../collab_ui2" } collections = { path = "../collections" } command_palette = { package="command_palette2", path = "../command_palette2" } # component_test = { path = "../component_test" } -# context_menu = { path = "../context_menu" } +context_menu = { package = "context_menu2", path = "../context_menu2" } client = { package = "client2", path = "../client2" } # clock = { path = "../clock" } copilot = { package = "copilot2", path = "../copilot2" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index ee1a067a29d1e09b4f6e1893d1268d52f634b065..581913b752fb8e07f8b6f594ea2a14f003a016c2 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -141,7 +141,7 @@ fn main() { cx.set_global(client.clone()); theme::init(cx); - // context_menu::init(cx); + context_menu::init(cx); project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx); From ab0a3f19ab363acbd2a9990e7fd0bb3d4e33bff8 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 16 Nov 2023 15:13:04 +0200 Subject: [PATCH 3/3] Add an actual context menu into terminal-2 (click the text, not the pane!) Co-Authored-By: Piotr --- Cargo.lock | 16 +- Cargo.toml | 1 - crates/context_menu2/Cargo.toml | 19 - crates/context_menu2/src/context_menu.rs | 557 --------------------- crates/terminal_view2/Cargo.toml | 1 - crates/terminal_view2/src/terminal_view.rs | 82 +-- crates/ui2/Cargo.toml | 1 + crates/ui2/src/components/context_menu.rs | 47 +- crates/ui2/src/components/label.rs | 2 +- crates/ui2/src/components/list.rs | 30 +- crates/zed2/Cargo.toml | 1 - crates/zed2/src/main.rs | 1 - 12 files changed, 112 insertions(+), 646 deletions(-) delete mode 100644 crates/context_menu2/Cargo.toml delete mode 100644 crates/context_menu2/src/context_menu.rs diff --git a/Cargo.lock b/Cargo.lock index 87c370f59e82cfa36f8452ca4477453739ea1391..bf2e964ea8f533a8a0c0ada0e60077fdd140caa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1989,19 +1989,6 @@ dependencies = [ "theme", ] -[[package]] -name = "context_menu2" -version = "0.1.0" -dependencies = [ - "anyhow", - "gpui2", - "menu2", - "settings2", - "smallvec", - "theme2", - "workspace2", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -9194,7 +9181,6 @@ version = "0.1.0" dependencies = [ "anyhow", "client2", - "context_menu2", "db2", "dirs 4.0.0", "editor2", @@ -10146,6 +10132,7 @@ dependencies = [ "chrono", "gpui2", "itertools 0.11.0", + "menu2", "rand 0.8.5", "serde", "settings2", @@ -11532,7 +11519,6 @@ dependencies = [ "collab_ui2", "collections", "command_palette2", - "context_menu2", "copilot2", "ctor", "db2", diff --git a/Cargo.toml b/Cargo.toml index 65c7630077e93cf8c23158ae175b359fdba3b7f4..f8d0af77fa85220f348c866edfe5242d0cccbeec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ members = [ "crates/command_palette2", "crates/component_test", "crates/context_menu", - "crates/context_menu2", "crates/copilot", "crates/copilot2", "crates/copilot_button", diff --git a/crates/context_menu2/Cargo.toml b/crates/context_menu2/Cargo.toml deleted file mode 100644 index 5c8aae03a1007d85c32df61ad64f5fcb0b5aca49..0000000000000000000000000000000000000000 --- a/crates/context_menu2/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "context_menu2" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -path = "src/context_menu.rs" -doctest = false - -[dependencies] -gpui = { package = "gpui2", path = "../gpui2" } -menu = { package = "menu2", path = "../menu2" } -settings = { package = "settings2", path = "../settings2" } -theme = { package = "theme2", path = "../theme2" } -workspace = { package = "workspace2", path = "../workspace2" } - -anyhow.workspace = true -smallvec.workspace = true diff --git a/crates/context_menu2/src/context_menu.rs b/crates/context_menu2/src/context_menu.rs deleted file mode 100644 index 885c5c8521a91e1a4a44ed362b8d498970fa1481..0000000000000000000000000000000000000000 --- a/crates/context_menu2/src/context_menu.rs +++ /dev/null @@ -1,557 +0,0 @@ -#![allow(unused_variables, unused)] -//todo!(remove) - -use gpui::{ - div, Action, AnchorCorner, AnyElement, AppContext, BorrowWindow, Div, EntityId, FocusHandle, - FocusableView, Pixels, Point, Render, ViewContext, -}; -use menu::*; - -use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration}; - -pub fn init(cx: &mut AppContext) { - // todo!() - // cx.observe_new_views( - // |workspace: &mut Workspace, _: &mut ViewContext| { - // workspace.register_action(ContextMenu::select_first); - // workspace.register_action(ContextMenu::select_last); - // workspace.register_action(ContextMenu::select_next); - // workspace.register_action(ContextMenu::select_prev); - // workspace.register_action(ContextMenu::confirm); - // workspace.register_action(ContextMenu::cancel); - // }, - // ) - // .detach(); -} - -pub type StaticItem = Box AnyElement>; - -type ContextMenuItemBuilder = (); -// todo!() -// Box AnyElement>; - -pub enum ContextMenuItemLabel { - String(Cow<'static, str>), - Element(ContextMenuItemBuilder), -} - -impl From> for ContextMenuItemLabel { - fn from(s: Cow<'static, str>) -> Self { - Self::String(s) - } -} - -impl From<&'static str> for ContextMenuItemLabel { - fn from(s: &'static str) -> Self { - Self::String(s.into()) - } -} - -impl From for ContextMenuItemLabel { - fn from(s: String) -> Self { - Self::String(s.into()) - } -} - -// todo!() -// impl From for ContextMenuItemLabel -// where -// T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement, -// { -// fn from(f: T) -> Self { -// Self::Element(Box::new(f)) -// } -// } - -pub enum ContextMenuItemAction { - Action(Box), - Handler(Arc)>), -} - -impl Clone for ContextMenuItemAction { - fn clone(&self) -> Self { - match self { - Self::Action(action) => Self::Action(action.boxed_clone()), - Self::Handler(handler) => Self::Handler(handler.clone()), - } - } -} - -pub enum ContextMenuItem { - Item { - label: ContextMenuItemLabel, - action: ContextMenuItemAction, - }, - Static(StaticItem), - Separator, -} - -impl ContextMenuItem { - pub fn action(label: impl Into, action: impl 'static + Action) -> Self { - Self::Item { - label: label.into(), - action: ContextMenuItemAction::Action(Box::new(action)), - } - } - - pub fn handler( - label: impl Into, - handler: impl 'static + Fn(&mut ViewContext), - ) -> Self { - Self::Item { - label: label.into(), - action: ContextMenuItemAction::Handler(Arc::new(handler)), - } - } - - pub fn separator() -> Self { - Self::Separator - } - - fn is_action(&self) -> bool { - matches!(self, Self::Item { .. }) - } - - fn action_id(&self) -> Option { - match self { - ContextMenuItem::Item { action, .. } => match action { - ContextMenuItemAction::Action(action) => Some(action.type_id()), - ContextMenuItemAction::Handler(_) => None, - }, - ContextMenuItem::Static(..) | ContextMenuItem::Separator => None, - } - } -} - -pub struct ContextMenu { - show_count: usize, - anchor_position: Point, - anchor_corner: AnchorCorner, - // todo!() - // position_mode: OverlayPositionMode, - items: Vec, - selected_index: Option, - visible: bool, - delay_cancel: bool, - previously_focused_view_handle: Option, - parent_view_id: EntityId, - focus_handle: FocusHandle, - // todo!() - // _actions_observation: Subscription, -} - -impl FocusableView for ContextMenu { - fn focus_handle(&self, _: &AppContext) -> FocusHandle { - self.focus_handle.clone() - } -} - -// todo!() -// fn ui_name() -> &'static str { -// "ContextMenu" -// } -// -// fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { -// Self::reset_to_default_keymap_context(keymap); -// keymap.add_identifier("menu"); -// } -// -// fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext) { -// self.reset(cx); -// } - -impl Render for ContextMenu { - type Element = Div; - - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - if !self.visible { - return div(); - } - - // todo!() - // // Render the menu once at minimum width. - // let mut collapsed_menu = self.render_menu_for_measurement(cx); - // let expanded_menu = self - // .render_menu(cx) - // .dynamically(move |constraint, view, cx| { - // SizeConstraint::strict_along( - // Axis::Horizontal, - // collapsed_menu.layout(constraint, view, cx).0.x(), - // ) - // }); - - // Overlay::new(expanded_menu) - // .with_hoverable(true) - // .with_fit_mode(OverlayFitMode::SnapToWindow) - // .with_anchor_position(self.anchor_position) - // .with_anchor_corner(self.anchor_corner) - // .with_position_mode(self.position_mode) - // .into_any() - div() - } -} - -impl ContextMenu { - pub fn new(parent_view_id: EntityId, cx: &mut ViewContext) -> Self { - Self { - show_count: 0, - delay_cancel: false, - anchor_position: Default::default(), - anchor_corner: AnchorCorner::TopLeft, - // todo!() - // position_mode: OverlayPositionMode::Window, - items: Default::default(), - selected_index: Default::default(), - visible: Default::default(), - previously_focused_view_handle: None, - parent_view_id, - // todo!() - // _actions_observation: cx.observe_actions(Self::action_dispatched), - focus_handle: cx.focus_handle(), - } - } - - pub fn visible(&self) -> bool { - self.visible - } - - fn action_dispatched(&mut self, action_id: TypeId, cx: &mut ViewContext) { - if let Some(ix) = self - .items - .iter() - .position(|item| item.action_id() == Some(action_id)) - { - self.selected_index = Some(ix); - cx.notify(); - cx.spawn(|this, mut cx| async move { - cx.background_executor() - .timer(Duration::from_millis(50)) - .await; - this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); - } - } - - fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - if let Some(ix) = self.selected_index { - if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) { - match action { - ContextMenuItemAction::Action(action) => { - let window = cx.window(); - let view_id = self.parent_view_id; - let action = action.boxed_clone(); - // todo!() - // cx.app_context() - // .spawn(|mut cx| async move { - // window - // .dispatch_action(view_id, action.as_ref(), &mut cx) - // .ok_or_else(|| anyhow!("window was closed")) - // }) - // .detach_and_log_err(cx); - } - ContextMenuItemAction::Handler(handler) => handler(cx), - } - self.reset(cx); - } - } - } - - pub fn delay_cancel(&mut self) { - self.delay_cancel = true; - } - - fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - if !self.delay_cancel { - self.reset(cx); - let show_count = self.show_count; - cx.defer(move |this, cx| { - if this.focus_handle.is_focused(cx) && this.show_count == show_count { - if let Some(previously_focused_view_handle) = - this.previously_focused_view_handle.take() - { - previously_focused_view_handle.focus(cx); - } - } - }); - } else { - self.delay_cancel = false; - } - } - - fn reset(&mut self, cx: &mut ViewContext) { - self.items.clear(); - self.visible = false; - self.selected_index.take(); - cx.notify(); - } - - fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext) { - self.selected_index = self.items.iter().position(|item| item.is_action()); - cx.notify(); - } - - fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext) { - for (ix, item) in self.items.iter().enumerate().rev() { - if item.is_action() { - self.selected_index = Some(ix); - cx.notify(); - break; - } - } - } - - fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext) { - if let Some(ix) = self.selected_index { - for (ix, item) in self.items.iter().enumerate().skip(ix + 1) { - if item.is_action() { - self.selected_index = Some(ix); - cx.notify(); - break; - } - } - } else { - self.select_first(&Default::default(), cx); - } - } - - fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext) { - if let Some(ix) = self.selected_index { - for (ix, item) in self.items.iter().enumerate().take(ix).rev() { - if item.is_action() { - self.selected_index = Some(ix); - cx.notify(); - break; - } - } - } else { - self.select_last(&Default::default(), cx); - } - } - - pub fn toggle( - &mut self, - anchor_position: Point, - anchor_corner: AnchorCorner, - items: Vec, - cx: &mut ViewContext, - ) { - if self.visible() { - self.cancel(&Cancel, cx); - } else { - let mut items = items.into_iter().peekable(); - if items.peek().is_some() { - self.items = items.collect(); - self.anchor_position = anchor_position; - self.anchor_corner = anchor_corner; - self.visible = true; - self.show_count += 1; - if !self.focus_handle.is_focused(cx) { - self.previously_focused_view_handle = cx.focused(); - } - cx.focus_self(); - } else { - self.visible = false; - } - } - cx.notify(); - } - - pub fn show( - &mut self, - anchor_position: Point, - anchor_corner: AnchorCorner, - items: Vec, - cx: &mut ViewContext, - ) { - let mut items = items.into_iter().peekable(); - if items.peek().is_some() { - self.items = items.collect(); - self.anchor_position = anchor_position; - self.anchor_corner = anchor_corner; - self.visible = true; - self.show_count += 1; - if !self.focus_handle.is_focused(cx) { - self.previously_focused_view_handle = cx.focused(); - } - cx.focus_self(); - } else { - self.visible = false; - } - cx.notify(); - } - - // todo!() - // pub fn set_position_mode(&mut self, mode: OverlayPositionMode) { - // self.position_mode = mode; - // } - - fn render_menu_for_measurement(&self, cx: &mut ViewContext) -> Div { - // let style = theme::current(cx).context_menu.clone(); - // Flex::row() - // .with_child( - // Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| { - // match item { - // ContextMenuItem::Item { label, .. } => { - // let style = style.item.in_state(self.selected_index == Some(ix)); - // let style = style.style_for(&mut Default::default()); - - // match label { - // ContextMenuItemLabel::String(label) => { - // Label::new(label.to_string(), style.label.clone()) - // .contained() - // .with_style(style.container) - // .into_any() - // } - // ContextMenuItemLabel::Element(element) => { - // element(&mut Default::default(), style) - // } - // } - // } - - // ContextMenuItem::Static(f) => f(cx), - - // ContextMenuItem::Separator => Empty::new() - // .collapsed() - // .contained() - // .with_style(style.separator) - // .constrained() - // .with_height(1.) - // .into_any(), - // } - // })), - // ) - // .with_child( - // Flex::column() - // .with_children(self.items.iter().enumerate().map(|(ix, item)| { - // match item { - // ContextMenuItem::Item { action, .. } => { - // let style = style.item.in_state(self.selected_index == Some(ix)); - // let style = style.style_for(&mut Default::default()); - - // match action { - // ContextMenuItemAction::Action(action) => KeystrokeLabel::new( - // self.parent_view_id, - // action.boxed_clone(), - // style.keystroke.container, - // style.keystroke.text.clone(), - // ) - // .into_any(), - // ContextMenuItemAction::Handler(_) => Empty::new().into_any(), - // } - // } - - // ContextMenuItem::Static(_) => Empty::new().into_any(), - - // ContextMenuItem::Separator => Empty::new() - // .collapsed() - // .constrained() - // .with_height(1.) - // .contained() - // .with_style(style.separator) - // .into_any(), - // } - // })) - // .contained() - // .with_margin_left(style.keystroke_margin), - // ) - // .contained() - // .with_style(style.container) - todo!() - } - - fn render_menu(&self, cx: &mut ViewContext) -> Div { - enum Menu {} - enum MenuItem {} - - // let style = theme::current(cx).context_menu.clone(); - - // MouseEventHandler::new::(0, cx, |_, cx| { - // Flex::column() - // .with_children(self.items.iter().enumerate().map(|(ix, item)| { - // match item { - // ContextMenuItem::Item { label, action } => { - // let action = action.clone(); - // let view_id = self.parent_view_id; - // MouseEventHandler::new::(ix, cx, |state, _| { - // let style = style.item.in_state(self.selected_index == Some(ix)); - // let style = style.style_for(state); - // let keystroke = match &action { - // ContextMenuItemAction::Action(action) => Some( - // KeystrokeLabel::new( - // view_id, - // action.boxed_clone(), - // style.keystroke.container, - // style.keystroke.text.clone(), - // ) - // .flex_float(), - // ), - // ContextMenuItemAction::Handler(_) => None, - // }; - - // Flex::row() - // .with_child(match label { - // ContextMenuItemLabel::String(label) => { - // Label::new(label.clone(), style.label.clone()) - // .contained() - // .into_any() - // } - // ContextMenuItemLabel::Element(element) => { - // element(state, style) - // } - // }) - // .with_children(keystroke) - // .contained() - // .with_style(style.container) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events - // .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events - // .on_click(MouseButton::Left, move |_, menu, cx| { - // menu.cancel(&Default::default(), cx); - // let window = cx.window(); - // match &action { - // ContextMenuItemAction::Action(action) => { - // let action = action.boxed_clone(); - // cx.app_context() - // .spawn(|mut cx| async move { - // window - // .dispatch_action( - // view_id, - // action.as_ref(), - // &mut cx, - // ) - // .ok_or_else(|| anyhow!("window was closed")) - // }) - // .detach_and_log_err(cx); - // } - // ContextMenuItemAction::Handler(handler) => handler(cx), - // } - // }) - // .on_drag(MouseButton::Left, |_, _, _| {}) - // .into_any() - // } - - // ContextMenuItem::Static(f) => f(cx), - - // ContextMenuItem::Separator => Empty::new() - // .constrained() - // .with_height(1.) - // .contained() - // .with_style(style.separator) - // .into_any(), - // } - // })) - // .contained() - // .with_style(style.container) - // }) - // .on_down_out(MouseButton::Left, |_, this, cx| { - // this.cancel(&Default::default(), cx); - // }) - // .on_down_out(MouseButton::Right, |_, this, cx| { - // this.cancel(&Default::default(), cx); - // }) - todo!() - } -} diff --git a/crates/terminal_view2/Cargo.toml b/crates/terminal_view2/Cargo.toml index 714ccb60a2ca3cfb2bdc651de2991dbd8d5a6486..12e2c06504fad5adc7e502c4225a28c9bf985f30 100644 --- a/crates/terminal_view2/Cargo.toml +++ b/crates/terminal_view2/Cargo.toml @@ -9,7 +9,6 @@ path = "src/terminal_view.rs" doctest = false [dependencies] -context_menu = { package = "context_menu2", path = "../context_menu2" } editor = { package = "editor2", path = "../editor2" } language = { package = "language2", path = "../language2" } gpui = { package = "gpui2", path = "../gpui2" } diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index ad47e720e25f6d0fbd3b7fba5f58c4bffc0faea7..14391ca2b2f357f56f084a5688601182bad780cf 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -7,12 +7,11 @@ pub mod terminal_panel; // todo!() // use crate::terminal_element::TerminalElement; -use context_menu::{ContextMenu, ContextMenuItem}; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, img, red, register_action, AnchorCorner, AnyElement, AppContext, Component, - DispatchPhase, Div, EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, - FocusableView, InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, + actions, div, img, red, register_action, AnyElement, AppContext, Component, DispatchPhase, Div, + EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, + InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton, ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, }; @@ -33,6 +32,7 @@ use workspace::{ notifications::NotifyResultExt, register_deserializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem}, + ui::{ContextMenu, ContextMenuItem, Label}, CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; @@ -66,6 +66,7 @@ pub struct SendKeystroke(String); actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest); pub fn init(cx: &mut AppContext) { + workspace::ui::init(cx); terminal_panel::init(cx); terminal::init(cx); @@ -86,7 +87,7 @@ pub struct TerminalView { has_new_content: bool, //Currently using iTerm bell, show bell emoji in tab until input is received has_bell: bool, - context_menu: View, + context_menu: Option, blink_state: bool, blinking_on: bool, blinking_paused: bool, @@ -269,7 +270,7 @@ impl TerminalView { has_new_content: true, has_bell: false, focus_handle: cx.focus_handle(), - context_menu: cx.build_view(|cx| ContextMenu::new(view_id, cx)), + context_menu: None, blink_state: true, blinking_on: false, blinking_paused: false, @@ -301,16 +302,15 @@ impl TerminalView { position: gpui::Point, cx: &mut ViewContext, ) { - let menu_entries = vec![ - ContextMenuItem::action("Clear", Clear), - ContextMenuItem::action("Close", CloseActiveItem { save_intent: None }), - ]; - - self.context_menu.update(cx, |menu, cx| { - menu.show(position, AnchorCorner::TopLeft, menu_entries, cx) - }); - - cx.notify(); + self.context_menu = Some(ContextMenu::new(vec![ + ContextMenuItem::entry(Label::new("Clear"), Clear), + ContextMenuItem::entry(Label::new("Close"), CloseActiveItem { save_intent: None }), + ])); + dbg!(&position); + // todo!() + // self.context_menu + // .show(position, AnchorCorner::TopLeft, menu_entries, cx); + // cx.notify(); } fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { @@ -547,27 +547,41 @@ impl Render for TerminalView { let focused = self.focus_handle.is_focused(cx); div() + .relative() + .child( + div() + .z_index(0) + .absolute() + .on_key_down(Self::key_down) + .on_action(TerminalView::send_text) + .on_action(TerminalView::send_keystroke) + .on_action(TerminalView::copy) + .on_action(TerminalView::paste) + .on_action(TerminalView::clear) + .on_action(TerminalView::show_character_palette) + .on_action(TerminalView::select_all) + // todo!() + .child( + "TERMINAL HERE", // TerminalElement::new( + // terminal_handle, + // focused, + // self.should_show_cursor(focused, cx), + // self.can_navigate_to_selected_word, + // ) + ) + .on_mouse_down(MouseButton::Right, |this, event, cx| { + this.deploy_context_menu(event.position, cx); + cx.notify(); + }), + ) + .children( + self.context_menu + .clone() + .map(|context_menu| div().z_index(1).absolute().child(context_menu.render())), + ) .track_focus(&self.focus_handle) .on_focus_in(Self::focus_in) .on_focus_out(Self::focus_out) - .on_key_down(Self::key_down) - .on_action(TerminalView::send_text) - .on_action(TerminalView::send_keystroke) - .on_action(TerminalView::copy) - .on_action(TerminalView::paste) - .on_action(TerminalView::clear) - .on_action(TerminalView::show_character_palette) - .on_action(TerminalView::select_all) - // todo!() - .child( - "TERMINAL HERE", // TerminalElement::new( - // terminal_handle, - // focused, - // self.should_show_cursor(focused, cx), - // self.can_navigate_to_selected_word, - // ) - ) - .child(self.context_menu.clone()) } } diff --git a/crates/ui2/Cargo.toml b/crates/ui2/Cargo.toml index 754bca371f41e741356f1ad9df670de2154e9978..0a7de6299d1f0999df813ced30902c039615f662 100644 --- a/crates/ui2/Cargo.toml +++ b/crates/ui2/Cargo.toml @@ -9,6 +9,7 @@ anyhow.workspace = true chrono = "0.4" gpui = { package = "gpui2", path = "../gpui2" } itertools = { version = "0.11.0", optional = true } +menu = { package = "menu2", path = "../menu2"} serde.workspace = true settings2 = { path = "../settings2" } smallvec.workspace = true diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 117be127798bd56f9d0c3963f8119832aa170550..8f32c3ed5664ede770af66580e0abe2f7379f767 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -3,17 +3,29 @@ use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHea pub enum ContextMenuItem { Header(SharedString), - Entry(Label), + Entry(Label, Box), Separator, } +impl Clone for ContextMenuItem { + fn clone(&self) -> Self { + match self { + ContextMenuItem::Header(name) => ContextMenuItem::Header(name.clone()), + ContextMenuItem::Entry(label, action) => { + ContextMenuItem::Entry(label.clone(), action.boxed_clone()) + } + ContextMenuItem::Separator => ContextMenuItem::Separator, + } + } +} impl ContextMenuItem { fn to_list_item(self) -> ListItem { match self { ContextMenuItem::Header(label) => ListSubHeader::new(label).into(), - ContextMenuItem::Entry(label) => { - ListEntry::new(label).variant(ListItemVariant::Inset).into() - } + ContextMenuItem::Entry(label, action) => ListEntry::new(label) + .variant(ListItemVariant::Inset) + .on_click(action) + .into(), ContextMenuItem::Separator => ListSeparator::new().into(), } } @@ -26,12 +38,12 @@ impl ContextMenuItem { Self::Separator } - pub fn entry(label: Label) -> Self { - Self::Entry(label) + pub fn entry(label: Label, action: impl Action) -> Self { + Self::Entry(label, Box::new(action)) } } -#[derive(Component)] +#[derive(Component, Clone)] pub struct ContextMenu { items: Vec, } @@ -42,7 +54,12 @@ impl ContextMenu { items: items.into_iter().collect(), } } - + // todo!() + // cx.add_action(ContextMenu::select_first); + // cx.add_action(ContextMenu::select_last); + // cx.add_action(ContextMenu::select_next); + // cx.add_action(ContextMenu::select_prev); + // cx.add_action(ContextMenu::confirm); fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { v_stack() .flex() @@ -55,9 +72,11 @@ impl ContextMenu { .map(ContextMenuItem::to_list_item::) .collect(), )) + .on_mouse_down_out(|_, _, cx| cx.dispatch_action(Box::new(menu::Cancel))) } } +use gpui::Action; #[cfg(feature = "stories")] pub use stories::*; @@ -65,7 +84,7 @@ pub use stories::*; mod stories { use super::*; use crate::story::Story; - use gpui::{Div, Render}; + use gpui::{action, Div, Render}; pub struct ContextMenuStory; @@ -73,14 +92,22 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + #[action] + struct PrintCurrentDate {} + Story::container(cx) .child(Story::title_for::<_, ContextMenu>(cx)) .child(Story::label(cx, "Default")) .child(ContextMenu::new([ ContextMenuItem::header("Section header"), ContextMenuItem::Separator, - ContextMenuItem::entry(Label::new("Some entry")), + ContextMenuItem::entry(Label::new("Print current time"), PrintCurrentDate {}), ])) + .on_action(|_, _: &PrintCurrentDate, _| { + if let Ok(unix_time) = std::time::UNIX_EPOCH.elapsed() { + println!("Current Unix time is {:?}", unix_time.as_secs()); + } + }) } } } diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index cbb75278c29487e0b5466421ce33f52342e60fed..1beee5c8b731bdcf984e53818bec4e960f6f41da 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -60,7 +60,7 @@ pub enum LineHeightStyle { UILabel, } -#[derive(Component)] +#[derive(Clone, Component)] pub struct Label { label: SharedString, size: LabelSize, diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 1ddad269dd752fbfce95028c11fcc555f7bbfeb0..4b355dd5b6c159c906117e64b3008f8d100129c7 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,11 +1,10 @@ -use gpui::div; +use gpui::{div, Action}; -use crate::prelude::*; use crate::settings::user_settings; use crate::{ - disclosure_control, h_stack, v_stack, Avatar, GraphicSlot, Icon, IconElement, IconSize, Label, - TextColor, Toggle, + disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, }; +use crate::{prelude::*, GraphicSlot}; #[derive(Clone, Copy, Default, Debug, PartialEq)] pub enum ListItemVariant { @@ -232,6 +231,7 @@ pub struct ListEntry { size: ListEntrySize, toggle: Toggle, variant: ListItemVariant, + on_click: Option>, } impl ListEntry { @@ -245,9 +245,15 @@ impl ListEntry { size: ListEntrySize::default(), toggle: Toggle::NotToggleable, variant: ListItemVariant::default(), + on_click: Default::default(), } } + pub fn on_click(mut self, action: impl Into>) -> Self { + self.on_click = Some(action.into()); + self + } + pub fn variant(mut self, variant: ListItemVariant) -> Self { self.variant = variant; self @@ -303,9 +309,21 @@ impl ListEntry { ListEntrySize::Small => div().h_6(), ListEntrySize::Medium => div().h_7(), }; - div() .relative() + .hover(|mut style| { + style.background = Some(cx.theme().colors().editor_background.into()); + style + }) + .on_mouse_down(gpui::MouseButton::Left, { + let action = self.on_click.map(|action| action.boxed_clone()); + + move |entry: &mut V, event, cx| { + if let Some(action) = action.as_ref() { + cx.dispatch_action(action.boxed_clone()); + } + } + }) .group("") .bg(cx.theme().colors().surface_background) // TODO: Add focus state @@ -401,7 +419,7 @@ impl List { v_stack() .w_full() .py_1() - .children(self.header) + .children(self.header.map(|header| header)) .child(list_content) } } diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 398a7a8920562c27e1093df31dab4c90211661f6..df3332803e790a7d7e32575487b40e041ee9e5cb 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -27,7 +27,6 @@ collab_ui = { package = "collab_ui2", path = "../collab_ui2" } collections = { path = "../collections" } command_palette = { package="command_palette2", path = "../command_palette2" } # component_test = { path = "../component_test" } -context_menu = { package = "context_menu2", path = "../context_menu2" } client = { package = "client2", path = "../client2" } # clock = { path = "../clock" } copilot = { package = "copilot2", path = "../copilot2" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 581913b752fb8e07f8b6f594ea2a14f003a016c2..ef65d00fc386503d68a6aaa457ecadabbf21e1a0 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -141,7 +141,6 @@ fn main() { cx.set_global(client.clone()); theme::init(cx); - context_menu::init(cx); project::Project::init(&client, cx); client::init(&client, cx); command_palette::init(cx);