From ceb5d0c69ef5ec777f1756ca25bf6d0caf988208 Mon Sep 17 00:00:00 2001 From: Federico Dionisi Date: Tue, 5 Dec 2023 19:31:20 +0100 Subject: [PATCH 01/20] Dismiss theme_selector on esc --- crates/theme_selector2/src/theme_selector.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/theme_selector2/src/theme_selector.rs b/crates/theme_selector2/src/theme_selector.rs index 582ce43a88ddc8c197617693adbf0e05ab7b11e3..3c6cae297c602706a766a230f77287a53640ff51 100644 --- a/crates/theme_selector2/src/theme_selector.rs +++ b/crates/theme_selector2/src/theme_selector.rs @@ -187,6 +187,10 @@ impl PickerDelegate for ThemeSelectorDelegate { Self::set_theme(self.original_theme.clone(), cx); self.selection_completed = true; } + + self.view + .update(cx, |_, cx| cx.emit(DismissEvent)) + .log_err(); } fn selected_index(&self) -> usize { From e96197d63bb7680807ef935cdaa7605c7d17788e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 10:27:25 +0100 Subject: [PATCH 02/20] Ensure editor gets focused when selecting conversation --- crates/assistant2/src/assistant_panel.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index e7c9d4c21a30ac98f4cd2e03fbd965cc74d1e40c..df215ad8e33f755887133d1e9b7683375736d931 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -891,8 +891,9 @@ impl AssistantPanel { if search_bar.show(cx) { search_bar.search_suggested(cx); if action.focus { + let focus_handle = search_bar.focus_handle(cx); search_bar.select_query(cx); - cx.focus_self(); + cx.focus(&focus_handle); } propagate = false } @@ -1028,6 +1029,8 @@ impl AssistantPanel { } fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext) -> Task> { + cx.focus(&self.focus_handle); + if let Some(ix) = self.editor_index_for_path(&path, cx) { self.set_active_editor_index(Some(ix), cx); return Task::ready(Ok(())); From 02bd4fb1f1eaa97f16ee6c08cab2716dcf133231 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 10:38:00 +0100 Subject: [PATCH 03/20] Simplify focus management in AssistantPanel --- crates/assistant2/src/assistant_panel.rs | 34 +++++++++--------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index df215ad8e33f755887133d1e9b7683375736d931..876225c7e7b4e448f70524fcd240605a8ba87fd6 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -331,7 +331,7 @@ impl AssistantPanel { let measurements = Rc::new(Cell::new(BlockMeasurements::default())); let inline_assistant = cx.build_view(|cx| { - let assistant = InlineAssistant::new( + InlineAssistant::new( inline_assist_id, measurements.clone(), self.include_conversation_in_next_inline_assist, @@ -342,9 +342,7 @@ impl AssistantPanel { self.retrieve_context_in_next_inline_assist, self.semantic_index.clone(), project.clone(), - ); - assistant.focus_handle.focus(cx); - assistant + ) }); let block_id = editor.update(cx, |editor, cx| { editor.change_selections(None, cx, |selections| { @@ -391,10 +389,7 @@ impl AssistantPanel { if let Some(inline_assistant) = inline_assistant.upgrade() { if let EditorEvent::SelectionsChanged { local } = event { if *local - && inline_assistant - .read(cx) - .focus_handle - .contains_focused(cx) + && inline_assistant.focus_handle(cx).contains_focused(cx) { cx.focus_view(&editor); } @@ -553,9 +548,12 @@ impl AssistantPanel { fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext) { if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) { if let Some(editor) = pending_assist.editor.upgrade() { - if let Some((block_id, _)) = pending_assist.inline_assistant.take() { + if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() { editor.update(cx, |editor, cx| { editor.remove_blocks(HashSet::from_iter([block_id]), None, cx); + if inline_assistant.focus_handle(cx).contains_focused(cx) { + editor.focus(cx); + } }); } } @@ -2029,7 +2027,6 @@ struct ConversationEditor { editor: View, blocks: HashSet, scroll_position: Option, - focus_handle: FocusHandle, _subscriptions: Vec, } @@ -2060,13 +2057,10 @@ impl ConversationEditor { editor }); - let focus_handle = cx.focus_handle(); - let _subscriptions = vec![ cx.observe(&conversation, |_, _, cx| cx.notify()), cx.subscribe(&conversation, Self::handle_conversation_event), cx.subscribe(&editor, Self::handle_editor_event), - cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.editor)), ]; let mut this = Self { @@ -2076,7 +2070,6 @@ impl ConversationEditor { scroll_position: None, fs, workspace, - focus_handle, _subscriptions, }; this.update_message_headers(cx); @@ -2487,8 +2480,8 @@ impl Render for ConversationEditor { } impl FocusableView for ConversationEditor { - fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { - self.focus_handle.clone() + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.editor.focus_handle(cx) } } @@ -2542,7 +2535,6 @@ struct InlineAssistant { prompt_editor: View, workspace: WeakView, confirmed: bool, - focus_handle: FocusHandle, include_conversation: bool, measurements: Rc>, prompt_history: VecDeque, @@ -2638,8 +2630,8 @@ impl Render for InlineAssistant { } impl FocusableView for InlineAssistant { - fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { - self.focus_handle.clone() + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.prompt_editor.focus_handle(cx) } } @@ -2665,12 +2657,11 @@ impl InlineAssistant { editor.set_placeholder_text(placeholder, cx); editor }); + cx.focus_view(&prompt_editor); - let focus_handle = cx.focus_handle(); let mut subscriptions = vec![ cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), - cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.prompt_editor)), ]; if let Some(semantic_index) = semantic_index.clone() { @@ -2682,7 +2673,6 @@ impl InlineAssistant { prompt_editor, workspace, confirmed: false, - focus_handle, include_conversation, measurements, prompt_history, From edb2f60d9d7fa2eb9f98d0848c70959447707333 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 10:53:24 +0100 Subject: [PATCH 04/20] Use the correct icons in AssistantPanel header --- crates/assistant2/src/assistant_panel.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 876225c7e7b4e448f70524fcd240605a8ba87fd6..359056d9d3d4826cbd9014261e0325c67069acd4 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -955,7 +955,7 @@ impl AssistantPanel { } fn render_split_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("split_button", Icon::Menu) + IconButton::new("split_button", Icon::SplitMessage) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); @@ -965,7 +965,7 @@ impl AssistantPanel { } fn render_assist_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("assist_button", Icon::Menu) + IconButton::new("assist_button", Icon::MagicWand) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); @@ -975,7 +975,7 @@ impl AssistantPanel { } fn render_quote_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("quote_button", Icon::Menu) + IconButton::new("quote_button", Icon::Quote) .on_click(cx.listener(|this, _event, cx| { if let Some(workspace) = this.workspace.upgrade() { cx.window_context().defer(move |cx| { @@ -989,7 +989,7 @@ impl AssistantPanel { } fn render_plus_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("plus_button", Icon::Menu) + IconButton::new("plus_button", Icon::Plus) .on_click(cx.listener(|this, _event, cx| { this.new_conversation(cx); })) @@ -998,7 +998,7 @@ impl AssistantPanel { fn render_zoom_button(&self, cx: &mut ViewContext) -> impl IntoElement { let zoomed = self.zoomed; - IconButton::new("zoom_button", Icon::Menu) + IconButton::new("zoom_button", Icon::MagnifyingGlass) .on_click(cx.listener(|this, _event, cx| { this.toggle_zoom(&ToggleZoom, cx); })) From a40515dbb3ed2ec063514357335029f4515a71c9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 12:10:49 +0100 Subject: [PATCH 05/20] Rework frame rendering This commit replaces the old `previous_frame` and `current_frame` with a new pair of `rendered_frame` and `next_frame` that are swapped once we are ready to draw a finished frame. This makes it clearer when to use which: `rendered_frame` should be used to query the existing state, whereas `next_frame` should be used and mutated when redrawing a dirty window. The change was prompted by a bug I encountered in `FocusHandle::contains`. The implementation was reading `current_frame`, but that was the wrong field to access if e.g. we were reading it inside of a `render` function or any other time while drawing the window. --- crates/gpui2/src/app.rs | 13 +- crates/gpui2/src/window.rs | 248 +++++++++++++++++-------------------- 2 files changed, 120 insertions(+), 141 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 9293302938c13b8948fab22dd4fc092e69da6c80..e23f46456940c8e3899ce2f11e07f68491c21099 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -704,14 +704,13 @@ impl AppContext { let focus_changed = focused.is_some() || blurred.is_some(); let event = FocusEvent { focused, blurred }; - let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); + let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners); if focus_changed { for listener in &mut listeners { listener(&event, cx); } } - listeners.extend(cx.window.current_frame.focus_listeners.drain(..)); - cx.window.current_frame.focus_listeners = listeners; + cx.window.rendered_frame.focus_listeners = listeners; if focus_changed { cx.window @@ -1029,9 +1028,13 @@ impl AppContext { window .update(self, |_, cx| { cx.window - .current_frame + .rendered_frame .dispatch_tree - .clear_pending_keystrokes() + .clear_pending_keystrokes(); + cx.window + .next_frame + .dispatch_tree + .clear_pending_keystrokes(); }) .ok(); } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 455472a349449dfa5e84d7a285a789a3402d35f6..1e26a7cf0705dbbf350724d0043a8ab79d3f3e92 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -90,7 +90,7 @@ impl FocusId { /// Obtains whether this handle contains the given handle in the most recently rendered frame. pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool { cx.window - .current_frame + .rendered_frame .dispatch_tree .focus_contains(*self, other) } @@ -212,8 +212,8 @@ pub struct Window { layout_engine: Option, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, - pub(crate) previous_frame: Frame, - pub(crate) current_frame: Frame, + pub(crate) rendered_frame: Frame, + pub(crate) next_frame: Frame, pub(crate) focus_handles: Arc>>, pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, default_prevented: bool, @@ -249,7 +249,7 @@ pub(crate) struct Frame { } impl Frame { - pub fn new(dispatch_tree: DispatchTree) -> Self { + fn new(dispatch_tree: DispatchTree) -> Self { Frame { element_states: HashMap::default(), mouse_listeners: HashMap::default(), @@ -262,6 +262,14 @@ impl Frame { element_offset_stack: Vec::new(), } } + + fn clear(&mut self) { + self.element_states.clear(); + self.mouse_listeners.values_mut().for_each(Vec::clear); + self.focus_listeners.clear(); + self.dispatch_tree.clear(); + self.depth_map.clear(); + } } impl Window { @@ -330,8 +338,8 @@ impl Window { layout_engine: Some(TaffyLayoutEngine::new()), root_view: None, element_id_stack: GlobalElementId::default(), - previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), - current_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), + rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), + next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), default_prevented: true, @@ -428,7 +436,7 @@ impl<'a> WindowContext<'a> { self.window.focus = Some(focus_id); self.window - .current_frame + .rendered_frame .dispatch_tree .clear_pending_keystrokes(); self.app.push_effect(Effect::FocusChanged { @@ -459,11 +467,11 @@ impl<'a> WindowContext<'a> { let node_id = focus_handle .and_then(|handle| { cx.window - .current_frame + .rendered_frame .dispatch_tree .focusable_node_id(handle.id) }) - .unwrap_or_else(|| cx.window.current_frame.dispatch_tree.root_node_id()); + .unwrap_or_else(|| cx.window.rendered_frame.dispatch_tree.root_node_id()); cx.propagate_event = true; cx.dispatch_action_on_node(node_id, action); @@ -743,7 +751,7 @@ impl<'a> WindowContext<'a> { self.window.default_prevented } - /// Register a mouse event listener on the window for the current frame. The type of event + /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. /// @@ -753,9 +761,9 @@ impl<'a> WindowContext<'a> { &mut self, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let order = self.window.current_frame.z_index_stack.clone(); + let order = self.window.next_frame.z_index_stack.clone(); self.window - .current_frame + .next_frame .mouse_listeners .entry(TypeId::of::()) .or_default() @@ -767,7 +775,7 @@ impl<'a> WindowContext<'a> { )) } - /// Register a key event listener on the window for the current frame. The type of event + /// Register a key event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. /// @@ -778,7 +786,7 @@ impl<'a> WindowContext<'a> { handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { self.window - .current_frame + .next_frame .dispatch_tree .on_key_event(Rc::new(move |event, phase, cx| { if let Some(event) = event.downcast_ref::() { @@ -787,7 +795,7 @@ impl<'a> WindowContext<'a> { })); } - /// Register an action listener on the window for the current frame. The type of action + /// Register an action listener on the window for the next frame. The type of action /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. /// @@ -798,7 +806,7 @@ impl<'a> WindowContext<'a> { action_type: TypeId, handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, ) { - self.window.current_frame.dispatch_tree.on_action( + self.window.next_frame.dispatch_tree.on_action( action_type, Rc::new(move |action, phase, cx| handler(action, phase, cx)), ); @@ -809,13 +817,13 @@ impl<'a> WindowContext<'a> { .focused() .and_then(|focused_handle| { self.window - .current_frame + .rendered_frame .dispatch_tree .focusable_node_id(focused_handle.id) }) - .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + .unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id()); self.window - .current_frame + .rendered_frame .dispatch_tree .is_action_available(action, target) } @@ -832,16 +840,16 @@ impl<'a> WindowContext<'a> { /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.current_frame.z_index_stack.push(z_index); + self.window.next_frame.z_index_stack.push(z_index); let result = f(self); - self.window.current_frame.z_index_stack.pop(); + self.window.next_frame.z_index_stack.pop(); result } /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { - let stacking_order = self.window.current_frame.z_index_stack.clone(); - let depth_map = &mut self.window.current_frame.depth_map; + let stacking_order = self.window.next_frame.z_index_stack.clone(); + let depth_map = &mut self.window.next_frame.depth_map; match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(&level)) { Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)), } @@ -850,7 +858,7 @@ impl<'a> WindowContext<'a> { /// Returns true if the top-most opaque layer painted over this point was part of the /// same layer as the given stacking order. pub fn was_top_layer(&self, point: &Point, level: &StackingOrder) -> bool { - for (stack, bounds) in self.window.previous_frame.depth_map.iter() { + for (stack, bounds) in self.window.rendered_frame.depth_map.iter() { if bounds.contains_point(point) { return level.starts_with(stack) || stack.starts_with(level); } @@ -861,10 +869,10 @@ impl<'a> WindowContext<'a> { /// Called during painting to get the current stacking order. pub fn stacking_order(&self) -> &StackingOrder { - &self.window.current_frame.z_index_stack + &self.window.next_frame.z_index_stack } - /// Paint one or more drop shadows into the scene for the current frame at the current z-index. + /// Paint one or more drop shadows into the scene for the next frame at the current z-index. pub fn paint_shadows( &mut self, bounds: Bounds, @@ -878,8 +886,8 @@ impl<'a> WindowContext<'a> { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, Shadow { order: 0, bounds: shadow_bounds.scale(scale_factor), @@ -892,7 +900,7 @@ impl<'a> WindowContext<'a> { } } - /// Paint one or more quads into the scene for the current frame at the current stacking context. + /// Paint one or more quads into the scene for the next frame at the current stacking context. /// Quads are colored rectangular regions with an optional background, border, and corner radius. pub fn paint_quad( &mut self, @@ -906,8 +914,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask(); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, Quad { order: 0, bounds: bounds.scale(scale_factor), @@ -920,20 +928,20 @@ impl<'a> WindowContext<'a> { ); } - /// Paint the given `Path` into the scene for the current frame at the current z-index. + /// Paint the given `Path` into the scene for the next frame at the current z-index. pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); path.content_mask = content_mask; path.color = color.into(); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, - path.scale(scale_factor), - ); + window + .next_frame + .scene_builder + .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); } - /// Paint an underline into the scene for the current frame at the current z-index. + /// Paint an underline into the scene for the next frame at the current z-index. pub fn paint_underline( &mut self, origin: Point, @@ -952,8 +960,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask(); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, Underline { order: 0, bounds: bounds.scale(scale_factor), @@ -965,7 +973,7 @@ impl<'a> WindowContext<'a> { ); } - /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. + /// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index. /// The y component of the origin is the baseline of the glyph. pub fn paint_glyph( &mut self, @@ -1005,8 +1013,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -1019,7 +1027,7 @@ impl<'a> WindowContext<'a> { Ok(()) } - /// Paint an emoji glyph into the scene for the current frame at the current z-index. + /// Paint an emoji glyph into the scene for the next frame at the current z-index. /// The y component of the origin is the baseline of the glyph. pub fn paint_emoji( &mut self, @@ -1056,8 +1064,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -1071,7 +1079,7 @@ impl<'a> WindowContext<'a> { Ok(()) } - /// Paint a monochrome SVG into the scene for the current frame at the current stacking context. + /// Paint a monochrome SVG into the scene for the next frame at the current stacking context. pub fn paint_svg( &mut self, bounds: Bounds, @@ -1098,8 +1106,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -1112,7 +1120,7 @@ impl<'a> WindowContext<'a> { Ok(()) } - /// Paint an image into the scene for the current frame at the current z-index. + /// Paint an image into the scene for the next frame at the current z-index. pub fn paint_image( &mut self, bounds: Bounds, @@ -1134,8 +1142,8 @@ impl<'a> WindowContext<'a> { let corner_radii = corner_radii.scale(scale_factor); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -1148,14 +1156,14 @@ impl<'a> WindowContext<'a> { Ok(()) } - /// Paint a surface into the scene for the current frame at the current z-index. + /// Paint a surface into the scene for the next frame at the current z-index. pub fn paint_surface(&mut self, bounds: Bounds, image_buffer: CVImageBuffer) { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.current_frame.scene_builder.insert( - &window.current_frame.z_index_stack, + window.next_frame.scene_builder.insert( + &window.next_frame.z_index_stack, Surface { order: 0, bounds, @@ -1167,15 +1175,17 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { + self.text_system().start_frame(); + self.window.platform_window.clear_input_handler(); + self.window.layout_engine.as_mut().unwrap().clear(); + self.window.next_frame.clear(); let root_view = self.window.root_view.take().unwrap(); - self.start_frame(); - self.with_z_index(0, |cx| { cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { for (action_type, action_listeners) in &cx.app.global_action_listeners { for action_listener in action_listeners.iter().cloned() { - cx.window.current_frame.dispatch_tree.on_action( + cx.window.next_frame.dispatch_tree.on_action( *action_type, Rc::new(move |action, phase, cx| action_listener(action, phase, cx)), ) @@ -1204,16 +1214,18 @@ impl<'a> WindowContext<'a> { } self.window - .current_frame + .next_frame .dispatch_tree .preserve_pending_keystrokes( - &mut self.window.previous_frame.dispatch_tree, + &mut self.window.rendered_frame.dispatch_tree, self.window.focus, ); - self.window.root_view = Some(root_view); - let scene = self.window.current_frame.scene_builder.build(); + let window = &mut self.window; + mem::swap(&mut window.rendered_frame, &mut window.next_frame); + + let scene = self.window.rendered_frame.scene_builder.build(); self.window.platform_window.draw(scene); let cursor_style = self .window @@ -1225,24 +1237,6 @@ impl<'a> WindowContext<'a> { self.window.dirty = false; } - /// Rotate the current frame and the previous frame, then clear the current frame. - /// We repopulate all state in the current frame during each paint. - fn start_frame(&mut self) { - self.window.platform_window.clear_input_handler(); - self.text_system().start_frame(); - - let window = &mut *self.window; - window.layout_engine.as_mut().unwrap().clear(); - - mem::swap(&mut window.previous_frame, &mut window.current_frame); - let frame = &mut window.current_frame; - frame.element_states.clear(); - frame.mouse_listeners.values_mut().for_each(Vec::clear); - frame.focus_listeners.clear(); - frame.dispatch_tree.clear(); - frame.depth_map.clear(); - } - /// Dispatch a mouse or keyboard event on the window. pub fn dispatch_event(&mut self, event: InputEvent) -> bool { // Handlers may set this to false by calling `stop_propagation` @@ -1321,7 +1315,7 @@ impl<'a> WindowContext<'a> { fn dispatch_mouse_event(&mut self, event: &dyn Any) { if let Some(mut handlers) = self .window - .current_frame + .rendered_frame .mouse_listeners .remove(&event.type_id()) { @@ -1351,17 +1345,8 @@ impl<'a> WindowContext<'a> { self.active_drag = None; } - // Just in case any handlers added new handlers, which is weird, but possible. - handlers.extend( - self.window - .current_frame - .mouse_listeners - .get_mut(&event.type_id()) - .into_iter() - .flat_map(|handlers| handlers.drain(..)), - ); self.window - .current_frame + .rendered_frame .mouse_listeners .insert(event.type_id(), handlers); } @@ -1373,15 +1358,15 @@ impl<'a> WindowContext<'a> { .focus .and_then(|focus_id| { self.window - .current_frame + .rendered_frame .dispatch_tree .focusable_node_id(focus_id) }) - .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + .unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id()); let dispatch_path = self .window - .current_frame + .rendered_frame .dispatch_tree .dispatch_path(node_id); @@ -1392,7 +1377,7 @@ impl<'a> WindowContext<'a> { self.propagate_event = true; for node_id in &dispatch_path { - let node = self.window.current_frame.dispatch_tree.node(*node_id); + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); if let Some(context) = node.context.clone() { context_stack.push(context); @@ -1409,7 +1394,7 @@ impl<'a> WindowContext<'a> { // Bubble phase for node_id in dispatch_path.iter().rev() { // Handle low level key events - let node = self.window.current_frame.dispatch_tree.node(*node_id); + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { key_listener(event, DispatchPhase::Bubble, self); if !self.propagate_event { @@ -1418,12 +1403,12 @@ impl<'a> WindowContext<'a> { } // Match keystrokes - let node = self.window.current_frame.dispatch_tree.node(*node_id); + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); if node.context.is_some() { if let Some(key_down_event) = event.downcast_ref::() { if let Some(found) = self .window - .current_frame + .rendered_frame .dispatch_tree .dispatch_key(&key_down_event.keystroke, &context_stack) { @@ -1446,13 +1431,13 @@ impl<'a> WindowContext<'a> { fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box) { let dispatch_path = self .window - .current_frame + .rendered_frame .dispatch_tree .dispatch_path(node_id); // Capture phase for node_id in &dispatch_path { - let node = self.window.current_frame.dispatch_tree.node(*node_id); + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for DispatchActionListener { action_type, listener, @@ -1469,7 +1454,7 @@ impl<'a> WindowContext<'a> { } // Bubble phase for node_id in dispatch_path.iter().rev() { - let node = self.window.current_frame.dispatch_tree.node(*node_id); + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for DispatchActionListener { action_type, listener, @@ -1529,25 +1514,25 @@ impl<'a> WindowContext<'a> { .focus .and_then(|focus_id| { self.window - .current_frame + .rendered_frame .dispatch_tree .focusable_node_id(focus_id) }) - .unwrap_or_else(|| self.window.current_frame.dispatch_tree.root_node_id()); + .unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id()); self.window - .current_frame + .rendered_frame .dispatch_tree .available_actions(node_id) } pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window - .previous_frame + .rendered_frame .dispatch_tree .bindings_for_action( action, - &self.window.previous_frame.dispatch_tree.context_stack, + &self.window.rendered_frame.dispatch_tree.context_stack, ) } @@ -1556,7 +1541,7 @@ impl<'a> WindowContext<'a> { action: &dyn Action, focus_handle: &FocusHandle, ) -> Vec { - let dispatch_tree = &self.window.previous_frame.dispatch_tree; + let dispatch_tree = &self.window.rendered_frame.dispatch_tree; let Some(node_id) = dispatch_tree.focusable_node_id(focus_handle.id) else { return vec![]; @@ -1599,24 +1584,21 @@ impl<'a> WindowContext<'a> { f: impl FnOnce(Option, &mut Self) -> R, ) -> R { let window = &mut self.window; - window - .current_frame - .dispatch_tree - .push_node(context.clone()); + window.next_frame.dispatch_tree.push_node(context.clone()); if let Some(focus_handle) = focus_handle.as_ref() { window - .current_frame + .next_frame .dispatch_tree .make_focusable(focus_handle.id); } let result = f(focus_handle, self); - self.window.current_frame.dispatch_tree.pop_node(); + self.window.next_frame.dispatch_tree.pop_node(); result } - /// Register a focus listener for the current frame only. It will be cleared + /// Register a focus listener for the next frame only. It will be cleared /// on the next frame render. You should use this method only from within elements, /// and we may want to enforce that better via a different context type. // todo!() Move this to `FrameContext` to emphasize its individuality? @@ -1625,7 +1607,7 @@ impl<'a> WindowContext<'a> { listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static, ) { self.window - .current_frame + .next_frame .focus_listeners .push(Box::new(move |event, cx| { listener(event, cx); @@ -1876,12 +1858,9 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { ) -> R { if let Some(mask) = mask { let mask = mask.intersect(&self.content_mask()); - self.window_mut() - .current_frame - .content_mask_stack - .push(mask); + self.window_mut().next_frame.content_mask_stack.push(mask); let result = f(self); - self.window_mut().current_frame.content_mask_stack.pop(); + self.window_mut().next_frame.content_mask_stack.pop(); result } else { f(self) @@ -1897,12 +1876,9 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { size: self.window().viewport_size, }, }; - self.window_mut() - .current_frame - .content_mask_stack - .push(mask); + self.window_mut().next_frame.content_mask_stack.push(mask); let result = f(self); - self.window_mut().current_frame.content_mask_stack.pop(); + self.window_mut().next_frame.content_mask_stack.pop(); result } @@ -1929,26 +1905,26 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(&mut Self) -> R, ) -> R { self.window_mut() - .current_frame + .next_frame .element_offset_stack .push(offset); let result = f(self); - self.window_mut().current_frame.element_offset_stack.pop(); + self.window_mut().next_frame.element_offset_stack.pop(); result } /// Obtain the current element offset. fn element_offset(&self) -> Point { self.window() - .current_frame + .next_frame .element_offset_stack .last() .copied() .unwrap_or_default() } - /// Update or intialize state for an element with the given id that lives across multiple - /// frames. If an element with this id existed in the previous frame, its state will be passed + /// Update or initialize state for an element with the given id that lives across multiple + /// frames. If an element with this id existed in the rendered frame, its state will be passed /// to the given closure. The state returned by the closure will be stored so it can be referenced /// when drawing the next frame. fn with_element_state( @@ -1964,12 +1940,12 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { if let Some(any) = cx .window_mut() - .current_frame + .next_frame .element_states .remove(&global_id) .or_else(|| { cx.window_mut() - .previous_frame + .rendered_frame .element_states .remove(&global_id) }) @@ -2011,7 +1987,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { let (result, state) = f(Some(state), cx); state_box.replace(state); cx.window_mut() - .current_frame + .next_frame .element_states .insert(global_id, ElementStateBox { inner: state_box, @@ -2023,7 +1999,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } else { let (result, state) = f(None, cx); cx.window_mut() - .current_frame + .next_frame .element_states .insert(global_id, ElementStateBox { @@ -2042,7 +2018,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { /// Obtain the current content mask. fn content_mask(&self) -> ContentMask { self.window() - .current_frame + .next_frame .content_mask_stack .last() .cloned() @@ -2130,9 +2106,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.current_frame.z_index_stack.push(z_index); + self.window.next_frame.z_index_stack.push(z_index); let result = f(self); - self.window.current_frame.z_index_stack.pop(); + self.window.next_frame.z_index_stack.pop(); result } From 45f7ab876f499616bef30bbfe771971bcbff0358 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:52:34 +0100 Subject: [PATCH 06/20] buffer search: Fix up rough edges Focus query editor when Deploy::focused is true, add missing bindings --- .../quick_action_bar2/src/quick_action_bar.rs | 8 +++-- crates/search2/src/buffer_search.rs | 31 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/quick_action_bar2/src/quick_action_bar.rs b/crates/quick_action_bar2/src/quick_action_bar.rs index e933689e62ee2f59e49aeeaf080032c489702fe6..3eba0e60ec20672b0b759d5e041745eb6c46e162 100644 --- a/crates/quick_action_bar2/src/quick_action_bar.rs +++ b/crates/quick_action_bar2/src/quick_action_bar.rs @@ -5,7 +5,7 @@ use gpui::{ Action, ClickEvent, Div, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Stateful, Styled, Subscription, View, ViewContext, WeakView, }; -use search::BufferSearchBar; +use search::{buffer_search, BufferSearchBar}; use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip}; use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -64,12 +64,14 @@ impl Render for QuickActionBar { "toggle buffer search", Icon::MagnifyingGlass, !self.buffer_search_bar.read(cx).is_dismissed(), - Box::new(search::buffer_search::Deploy { focus: false }), + Box::new(buffer_search::Deploy { focus: false }), "Buffer Search", { let buffer_search_bar = self.buffer_search_bar.clone(); move |_, cx| { - buffer_search_bar.update(cx, |search_bar, cx| search_bar.toggle(cx)); + buffer_search_bar.update(cx, |search_bar, cx| { + search_bar.toggle(&buffer_search::Deploy { focus: true }, cx) + }); } }, )) diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index da32f51194cc68d869a76ba8b88023f5450b2844..4ea0226d48c6aa9e96c957df983bdb3740596b05 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -11,8 +11,8 @@ use editor::{Editor, EditorMode}; use futures::channel::oneshot; use gpui::{ actions, div, red, Action, AppContext, Div, EventEmitter, FocusableView, - InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task, - View, ViewContext, VisualContext as _, WeakView, WindowContext, + InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, Styled, + Subscription, Task, View, ViewContext, VisualContext as _, WeakView, WindowContext, }; use project::search::SearchQuery; use serde::Deserialize; @@ -170,14 +170,19 @@ impl Render for BufferSearchBar { h_stack() .key_context("BufferSearchBar") - .when(in_replace, |this| { - this.key_context("in_replace") - .on_action(cx.listener(Self::replace_next)) - .on_action(cx.listener(Self::replace_all)) - }) .on_action(cx.listener(Self::previous_history_query)) .on_action(cx.listener(Self::next_history_query)) .on_action(cx.listener(Self::dismiss)) + .on_action(cx.listener(Self::select_next_match)) + .on_action(cx.listener(Self::select_prev_match)) + .when(self.supported_options().replacement, |this| { + this.on_action(cx.listener(Self::toggle_replace)) + .when(in_replace, |this| { + this.key_context("in_replace") + .on_action(cx.listener(Self::replace_next)) + .on_action(cx.listener(Self::replace_all)) + }) + }) .w_full() .p_1() .child( @@ -305,7 +310,7 @@ impl BufferSearchBar { let handle = cx.view().downgrade(); - editor.register_action(move |a: &Deploy, cx| { + editor.register_action(move |deploy: &Deploy, cx| { let Some(pane) = handle.upgrade().and_then(|editor| editor.read(cx).pane(cx)) else { return; }; @@ -313,12 +318,12 @@ impl BufferSearchBar { pane.update(cx, |this, cx| { this.toolbar().update(cx, |this, cx| { if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| this.toggle(cx)); + search_bar.update(cx, |this, cx| this.toggle(deploy, cx)); return; } let view = cx.build_view(|cx| BufferSearchBar::new(cx)); this.add_item(view.clone(), cx); - view.update(cx, |this, cx| this.deploy(a, cx)); + view.update(cx, |this, cx| this.deploy(deploy, cx)); cx.notify(); }) }); @@ -468,7 +473,7 @@ impl BufferSearchBar { self.search_suggested(cx); if deploy.focus { self.select_query(cx); - let handle = cx.focus_handle(); + let handle = self.query_editor.focus_handle(cx); cx.focus(&handle); } return true; @@ -477,9 +482,9 @@ impl BufferSearchBar { false } - pub fn toggle(&mut self, cx: &mut ViewContext) { + pub fn toggle(&mut self, action: &Deploy, cx: &mut ViewContext) { if self.is_dismissed() { - self.show(cx); + self.deploy(action, cx); } else { self.dismiss(&Dismiss, cx); } From 7842fdeb3200b9b783b7170fb9840019f562c9f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 12:54:28 +0100 Subject: [PATCH 07/20] Fix confirming rename not working on editor2 --- crates/editor2/src/editor.rs | 24 +++---- crates/editor2/src/element.rs | 115 +++++++++++++++++----------------- 2 files changed, 70 insertions(+), 69 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 05b459803cfac270431e681ae35c58fc60c49add..46d64fcf9d4c8f0860d741be8dc3016c17d18bc0 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1814,34 +1814,34 @@ impl Editor { this } - fn dispatch_context(&self, cx: &AppContext) -> KeyContext { - let mut dispatch_context = KeyContext::default(); - dispatch_context.add("Editor"); + fn key_context(&self, cx: &AppContext) -> KeyContext { + let mut key_context = KeyContext::default(); + key_context.add("Editor"); let mode = match self.mode { EditorMode::SingleLine => "single_line", EditorMode::AutoHeight { .. } => "auto_height", EditorMode::Full => "full", }; - dispatch_context.set("mode", mode); + key_context.set("mode", mode); if self.pending_rename.is_some() { - dispatch_context.add("renaming"); + key_context.add("renaming"); } if self.context_menu_visible() { match self.context_menu.read().as_ref() { Some(ContextMenu::Completions(_)) => { - dispatch_context.add("menu"); - dispatch_context.add("showing_completions") + key_context.add("menu"); + key_context.add("showing_completions") } Some(ContextMenu::CodeActions(_)) => { - dispatch_context.add("menu"); - dispatch_context.add("showing_code_actions") + key_context.add("menu"); + key_context.add("showing_code_actions") } None => {} } } for layer in self.keymap_context_layers.values() { - dispatch_context.extend(layer); + key_context.extend(layer); } if let Some(extension) = self @@ -1850,10 +1850,10 @@ impl Editor { .as_singleton() .and_then(|buffer| buffer.read(cx).file()?.path().extension()?.to_str()) { - dispatch_context.set("extension", extension.to_string()); + key_context.set("extension", extension.to_string()); } - dispatch_context + key_context } pub fn new_file( diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 9447c3370b45ad131d3907bbae59eff889548331..ad66ed8090749ab3d69b71a9aa30d7b77e701465 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -275,36 +275,48 @@ impl EditorElement { register_action(view, cx, Editor::copy_relative_path); register_action(view, cx, Editor::copy_highlight_json); register_action(view, cx, |editor, action, cx| { - editor - .format(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.format(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, Editor::restart_language_server); register_action(view, cx, Editor::show_character_palette); register_action(view, cx, |editor, action, cx| { - editor - .confirm_completion(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.confirm_completion(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, |editor, action, cx| { - editor - .confirm_code_action(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.confirm_code_action(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, |editor, action, cx| { - editor - .rename(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.rename(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, |editor, action, cx| { - editor - .confirm_rename(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.confirm_rename(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, |editor, action, cx| { - editor - .find_all_references(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.find_all_references(action, cx) { + task.detach_and_log_err(cx); + } else { + cx.propagate(); + } }); register_action(view, cx, Editor::next_copilot_suggestion); register_action(view, cx, Editor::previous_copilot_suggestion); @@ -2802,49 +2814,38 @@ impl Element for EditorElement { }; let focus_handle = editor.focus_handle(cx); - let dispatch_context = self.editor.read(cx).dispatch_context(cx); - cx.with_key_dispatch( - Some(dispatch_context), - Some(focus_handle.clone()), - |_, cx| { - self.register_actions(cx); - self.register_key_listeners(cx); - - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor - // take precedence. - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners( - bounds, - gutter_bounds, - text_bounds, - &layout, - cx, - ); - }); - let input_handler = - ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + let key_context = self.editor.read(cx).key_context(cx); + cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { + self.register_actions(cx); + self.register_key_listeners(cx); + + // We call with_z_index to establish a new stacking context. + cx.with_z_index(0, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor + // take precedence. + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); + }); + let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); - if !layout.blocks.is_empty() { - cx.with_z_index(1, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }) + if !layout.blocks.is_empty() { + cx.with_z_index(1, |cx| { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); }) - } - }); + }) + } }); - }, - ) + }); + }) } } From 18393d1fac311d1205318c35b0e0a7e703a0bd7d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:13:38 +0100 Subject: [PATCH 08/20] fixup! buffer search: Fix up rough edges --- crates/search2/src/buffer_search.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index 4ea0226d48c6aa9e96c957df983bdb3740596b05..285f37d5cbba7e296d692d3a2f1c9551c543ecdb 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -183,6 +183,12 @@ impl Render for BufferSearchBar { .on_action(cx.listener(Self::replace_all)) }) }) + .when(self.supported_options().case, |this| { + this.on_action(cx.listener(Self::toggle_case_sensitive)) + }) + .when(self.supported_options().word, |this| { + this.on_action(cx.listener(Self::toggle_whole_word)) + }) .w_full() .p_1() .child( From 8839bfa1df8018994ebbba90f672b5f882823d3b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:54:51 +0100 Subject: [PATCH 09/20] Remove unused import --- crates/search2/src/buffer_search.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index 285f37d5cbba7e296d692d3a2f1c9551c543ecdb..cbbeeb0f1260a57a9907e854c9aeb666c078fd90 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -11,8 +11,8 @@ use editor::{Editor, EditorMode}; use futures::channel::oneshot; use gpui::{ actions, div, red, Action, AppContext, Div, EventEmitter, FocusableView, - InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, Styled, - Subscription, Task, View, ViewContext, VisualContext as _, WeakView, WindowContext, + InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task, + View, ViewContext, VisualContext as _, WeakView, WindowContext, }; use project::search::SearchQuery; use serde::Deserialize; From 75324abbb5a60897020167e21b4033cc556628ad Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 10:41:29 +0200 Subject: [PATCH 10/20] Do not panic when focusing empty dock --- crates/workspace2/src/dock.rs | 19 ++++++++++++++----- crates/workspace2/src/workspace2.rs | 6 +++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index a0a90293d603a3afde61dda2c4ca59ec5fa273a1..7bae7bc4199631f0b538a99d45ecb6e1d7a4aaa5 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -133,13 +133,13 @@ pub struct Dock { panel_entries: Vec, is_open: bool, active_panel_index: usize, + focus_handle: FocusHandle, + focus_subscription: Subscription, } impl FocusableView for Dock { - fn focus_handle(&self, cx: &AppContext) -> FocusHandle { - self.panel_entries[self.active_panel_index] - .panel - .focus_handle(cx) + fn focus_handle(&self, _: &AppContext) -> FocusHandle { + self.focus_handle.clone() } } @@ -190,12 +190,20 @@ pub struct PanelButtons { } impl Dock { - pub fn new(position: DockPosition) -> Self { + pub fn new(position: DockPosition, cx: &mut ViewContext<'_, Self>) -> Self { + let focus_handle = cx.focus_handle(); + let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| { + if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) { + active_entry.panel.focus_handle(cx).focus(cx) + } + }); Self { position, panel_entries: Default::default(), active_panel_index: 0, is_open: false, + focus_handle, + focus_subscription, } } @@ -207,6 +215,7 @@ impl Dock { self.is_open } + // todo!() // pub fn has_focus(&self, cx: &WindowContext) -> bool { // self.visible_panel() // .map_or(false, |panel| panel.has_focus(cx)) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index abf908992978ff8e870eb88efe72f7ce0e332c41..50eb69eec5b857b25cf199e8e6b142b73d4bc0a0 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -566,9 +566,9 @@ impl Workspace { cx.emit(Event::WorkspaceCreated(weak_handle.clone())); - let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left)); - let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom)); - let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right)); + let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx)); + let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx)); + let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx)); let left_dock_buttons = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); let bottom_dock_buttons = From 646b74e0a77d74ecf8ba7b73e138bb950b307e91 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 11:20:29 +0200 Subject: [PATCH 11/20] Move actions to namespaces used in zed1 keybinding settings --- crates/recent_projects2/src/projects.rs | 1 + crates/recent_projects2/src/recent_projects.rs | 7 ++++--- crates/terminal2/src/terminal2.rs | 12 +++++++++++- crates/terminal_view2/src/terminal_view.rs | 10 ++++------ 4 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 crates/recent_projects2/src/projects.rs diff --git a/crates/recent_projects2/src/projects.rs b/crates/recent_projects2/src/projects.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c4809dbd7635cf619002ffcac15d5feb3a1c3c2 --- /dev/null +++ b/crates/recent_projects2/src/projects.rs @@ -0,0 +1 @@ +gpui::actions!(OpenRecent); diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index 0c11f2b9c7d9d579aa5de810078236bccec432f4..d2c13b40a33645f14060cc421a8c43c2b19b7c8f 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -1,9 +1,10 @@ mod highlighted_workspace_location; +mod projects; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, - View, ViewContext, WeakView, + AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, Result, Task, View, + ViewContext, WeakView, }; use highlighted_workspace_location::HighlightedWorkspaceLocation; use ordered_float::OrderedFloat; @@ -16,7 +17,7 @@ use workspace::{ WORKSPACE_DB, }; -actions!(OpenRecent); +pub use projects::OpenRecent; pub fn init(cx: &mut AppContext) { cx.observe_new_views(RecentProjects::register).detach(); diff --git a/crates/terminal2/src/terminal2.rs b/crates/terminal2/src/terminal2.rs index 6036d65d6e8c59d3aff234952980564ec9f71bec..b429bda710aa09d61fe26ba259353f55ab64d5ec 100644 --- a/crates/terminal2/src/terminal2.rs +++ b/crates/terminal2/src/terminal2.rs @@ -50,7 +50,7 @@ use std::{ use thiserror::Error; use gpui::{ - px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke, + actions, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollWheelEvent, Size, Task, TouchPhase, }; @@ -58,6 +58,16 @@ use gpui::{ use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str}; use lazy_static::lazy_static; +actions!( + Clear, + Copy, + Paste, + ShowCharacterPalette, + SearchTest, + SendText, + SendKeystroke, +); + ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable ///Scroll multiplier that is set to 3 by default. This will be removed when I ///Implement scroll bars. diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 75b019f2ef24639d12577854c59ceedb48607669..5a81b494b300a09a947b4012f2e7460859df996c 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -9,9 +9,9 @@ pub mod terminal_panel; // use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, - Focusable, FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, - MouseButton, MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView, + div, Action, AnyElement, AppContext, Div, EventEmitter, FocusEvent, FocusHandle, Focusable, + FocusableElement, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, + MouseDownEvent, Pixels, Render, Subscription, Task, View, VisualContext, WeakView, }; use language::Bias; use persistence::TERMINAL_DB; @@ -22,7 +22,7 @@ use terminal::{ term::{search::RegexSearch, TermMode}, }, terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory}, - Event, MaybeNavigationTarget, Terminal, + Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; @@ -60,8 +60,6 @@ pub struct SendText(String); #[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)] pub struct SendKeystroke(String); -actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest); - pub fn init(cx: &mut AppContext) { terminal_panel::init(cx); terminal::init(cx); From 2a82dff2fea545d685b4557525efee1b59ef9285 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 12:03:36 +0200 Subject: [PATCH 12/20] Suppress unused variable r-a warning from action derive procmacro --- crates/gpui2_macros/src/action.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs index abc75a875984842496c077f6e1841f5ec9915730..296e3f08b12b5fe0ede78d9ec1be0df96dd85735 100644 --- a/crates/gpui2_macros/src/action.rs +++ b/crates/gpui2_macros/src/action.rs @@ -38,6 +38,7 @@ pub fn action(input: TokenStream) -> TokenStream { let build_impl = if is_unit_struct { quote! { + let _ = value; Ok(std::boxed::Box::new(Self {})) } } else { From f829120f51f96f607889fce194ef5898cb7430e5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 14:34:36 +0200 Subject: [PATCH 13/20] Convert OS file drag and drop enter event into a mouse move, not mouse click --- crates/gpui2/src/window.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 1e26a7cf0705dbbf350724d0043a8ab79d3f3e92..a668e5d120d06390d402d70a874d9f1ac62cfe8f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1269,10 +1269,9 @@ impl<'a> WindowContext<'a> { cursor_offset: position, }); } - InputEvent::MouseDown(MouseDownEvent { + InputEvent::MouseMove(MouseMoveEvent { position, - button: MouseButton::Left, - click_count: 1, + pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), }) } From b692b4c9c241a87c33b930cf169b7ffcaa0fd0b2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 14:44:44 +0200 Subject: [PATCH 14/20] Do not expand terminal selections on active drag --- crates/gpui2/src/app.rs | 4 ++++ crates/gpui2/src/window.rs | 12 ++++++------ crates/terminal_view2/src/terminal_element.rs | 18 ++++++++---------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index e23f46456940c8e3899ce2f11e07f68491c21099..7d9ca68a82e05279b35e2a23c5c87b25be4315e5 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1110,6 +1110,10 @@ impl AppContext { } } } + + pub fn has_active_drag(&self) -> bool { + self.active_drag.is_some() + } } impl Context for AppContext { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a668e5d120d06390d402d70a874d9f1ac62cfe8f..12d6715653574336de06cef1e1a0390742318c1c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -4,12 +4,12 @@ use crate::{ DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, - ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, - Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, + Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, + WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::HashMap; diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 4a37ca91b779e6aa9c09fe510a1f5af9bbe6c48b..9153a43dfc58807519b0894c1b1b7964ea9b1c17 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -1,9 +1,9 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, point, px, red, relative, transparent_black, AnyElement, AsyncWindowContext, - AvailableSpace, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, - FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement, - LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, + AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, + FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, + IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, View, WhiteSpace, WindowContext, }; @@ -643,13 +643,11 @@ impl TerminalElement { let connection = connection.clone(); let focus = focus.clone(); move |e, cx| { - if e.pressed_button.is_some() { - if focus.is_focused(cx) { - connection.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - }) - } + if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { + connection.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); + cx.notify(); + }) } } }) From 6c653b9c05c546d58bd9f6358938f8f0c9d1a54e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 15:18:23 +0200 Subject: [PATCH 15/20] Activate Zed window on external file drop --- crates/gpui2/src/interactive.rs | 6 ++++++ crates/gpui2/src/window.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 2d48ec5a1100e61d493dd4ac92864915e4ce70bb..84636630f37b7216b94b85be68d52d1122c019b4 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -193,6 +193,12 @@ impl Deref for MouseExitEvent { #[derive(Debug, Clone, Default)] pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>); +impl ExternalPaths { + pub fn paths(&self) -> &[PathBuf] { + &self.0 + } +} + impl Render for ExternalPaths { type Element = Div; diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 12d6715653574336de06cef1e1a0390742318c1c..cab41067ce576dcab09d12537ab1da665dba4be4 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1284,6 +1284,7 @@ impl<'a> WindowContext<'a> { }) } FileDropEvent::Submit { position } => { + self.activate(true); self.window.mouse_position = position; InputEvent::MouseUp(MouseUpEvent { button: MouseButton::Left, From b9a904e55227197be1bef1fbb6f0edbec3392a6c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 7 Dec 2023 15:36:05 +0200 Subject: [PATCH 16/20] Add initial OS file drag and drop support --- crates/terminal_view2/src/terminal_element.rs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 9153a43dfc58807519b0894c1b1b7964ea9b1c17..d61ba5988e23f3ccfe5adb9264b549859373adf7 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -804,7 +804,28 @@ impl Element for TerminalElement { .map(|cursor| cursor.bounding_rect(origin)), }; - let mut this = self.register_mouse_listeners(origin, layout.mode, bounds, cx); + let terminal_focus_handle = self.focus.clone(); + let terminal_handle = self.terminal.clone(); + let mut this: TerminalElement = self + .register_mouse_listeners(origin, layout.mode, bounds, cx) + .drag_over::(|style| { + // todo!() why does not it work? z-index of elements? + style.bg(cx.theme().colors().ghost_element_hover) + }) + .on_drop::(move |external_paths, cx| { + cx.focus(&terminal_focus_handle); + let mut new_text = external_paths + .read(cx) + .paths() + .iter() + .map(|path| format!(" {path:?}")) + .join(""); + new_text.push(' '); + terminal_handle.update(cx, |terminal, _| { + // todo!() long paths are not displayed properly albeit the text is there + terminal.paste(&new_text); + }); + }); let interactivity = mem::take(&mut this.interactivity); From 08c4e1abdc6f8d9aaf50fb449ecfec802a76b395 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 17:42:37 +0100 Subject: [PATCH 17/20] Fix clipping when rendering paths Co-Authored-By: Nathan --- crates/gpui2/src/platform/mac/metal_renderer.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index 19afb503324907a0b84bcac7722fc71ead05451d..c477440df5d888658e96c8e9d4d3f7e70d214679 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -325,7 +325,7 @@ impl MetalRenderer { .entry(tile.texture_id) .or_insert(Vec::new()) .extend(path.vertices.iter().map(|vertex| PathVertex { - xy_position: vertex.xy_position - path.bounds.origin + xy_position: vertex.xy_position - clipped_bounds.origin + tile.bounds.origin.map(Into::into), st_position: vertex.st_position, content_mask: ContentMask { @@ -544,9 +544,10 @@ impl MetalRenderer { if let Some((path, tile)) = paths_and_tiles.peek() { if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) { prev_texture_id = Some(tile.texture_id); + let origin = path.bounds.intersect(&path.content_mask.bounds).origin; sprites.push(PathSprite { bounds: Bounds { - origin: path.bounds.origin.map(|p| p.floor()), + origin: origin.map(|p| p.floor()), size: tile.bounds.size.map(Into::into), }, color: path.color, From 2461902086d8ee56c43ea7c5d013a220e602e6f4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Dec 2023 17:51:59 +0100 Subject: [PATCH 18/20] Don't panic when trying to reuse an existing workspace Co-Authored-By: Nathan --- crates/workspace2/src/workspace2.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index abf908992978ff8e870eb88efe72f7ce0e332c41..e2965bb8d7ace47c29039e09ba66838fb4f5c256 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -4188,14 +4188,14 @@ pub fn open_paths( }); cx.spawn(move |mut cx| async move { if let Some(existing) = existing { - // // Ok(( - // existing.clone(), - // cx.update_window_root(&existing, |workspace, cx| { - // workspace.open_paths(abs_paths, true, cx) - // })? - // .await, - // )) - todo!() + Ok(( + existing.clone(), + existing + .update(&mut cx, |workspace, cx| { + workspace.open_paths(abs_paths, true, cx) + })? + .await, + )) } else { cx.update(move |cx| { Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx) From 393be3cedfe6775e5607a6cd879ae1ac421044c5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 7 Dec 2023 12:30:43 -0500 Subject: [PATCH 19/20] Extract `Tab` component (#3539) This PR extracts a new `Tab` component from the tabs implementation in the workspace. This will allow us to reuse this component anywhere that we need to use tabs. Like our other newer components, the `Tab` component has a relatively open API. It accepts `children` (or `child`) as well as a `start_slot` and `end_slot` to position content in the slots on either end of the content. These slots also respect the `TabCloseSide` and will switch positions based on this value. Screenshot 2023-12-07 at 12 19 42 PM Release Notes: - N/A --- crates/storybook2/src/story_selector.rs | 2 + crates/ui2/src/components.rs | 2 + crates/ui2/src/components/stories.rs | 2 + crates/ui2/src/components/stories/tab.rs | 114 +++++++++++++ crates/ui2/src/components/tab.rs | 198 +++++++++++++++++++++++ crates/workspace2/src/pane.rs | 133 +++++---------- 6 files changed, 358 insertions(+), 93 deletions(-) create mode 100644 crates/ui2/src/components/stories/tab.rs create mode 100644 crates/ui2/src/components/tab.rs diff --git a/crates/storybook2/src/story_selector.rs b/crates/storybook2/src/story_selector.rs index 216762060d83870e14177449befc5a0079332148..1e899a5783e28226f7c0bda65da4c9c066a7a81b 100644 --- a/crates/storybook2/src/story_selector.rs +++ b/crates/storybook2/src/story_selector.rs @@ -28,6 +28,7 @@ pub enum ComponentStory { ListHeader, ListItem, Scroll, + Tab, Text, ZIndex, Picker, @@ -53,6 +54,7 @@ impl ComponentStory { Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(), Self::Scroll => ScrollStory::view(cx).into(), Self::Text => TextStory::view(cx).into(), + Self::Tab => cx.build_view(|_| ui::TabStory).into(), Self::ZIndex => cx.build_view(|_| ZIndexStory).into(), Self::Picker => PickerStory::new(cx).into(), } diff --git a/crates/ui2/src/components.rs b/crates/ui2/src/components.rs index 583b30a2e05fd647d7603d41ee4f0edbf8510713..5bda108a87cd6deca8b10eb81d0bdcb4c0295689 100644 --- a/crates/ui2/src/components.rs +++ b/crates/ui2/src/components.rs @@ -13,6 +13,7 @@ mod popover; mod popover_menu; mod right_click_menu; mod stack; +mod tab; mod tooltip; #[cfg(feature = "stories")] @@ -33,6 +34,7 @@ pub use popover::*; pub use popover_menu::*; pub use right_click_menu::*; pub use stack::*; +pub use tab::*; pub use tooltip::*; #[cfg(feature = "stories")] diff --git a/crates/ui2/src/components/stories.rs b/crates/ui2/src/components/stories.rs index 113c2679b7f2b9cfec58d92d51060f7712b8a384..c54ee99057951bd76b37bb464d20226a5abf1d0f 100644 --- a/crates/ui2/src/components/stories.rs +++ b/crates/ui2/src/components/stories.rs @@ -10,6 +10,7 @@ mod label; mod list; mod list_header; mod list_item; +mod tab; pub use avatar::*; pub use button::*; @@ -23,3 +24,4 @@ pub use label::*; pub use list::*; pub use list_header::*; pub use list_item::*; +pub use tab::*; diff --git a/crates/ui2/src/components/stories/tab.rs b/crates/ui2/src/components/stories/tab.rs new file mode 100644 index 0000000000000000000000000000000000000000..cd5e920396a1f6803f261537fcf0a577621a6496 --- /dev/null +++ b/crates/ui2/src/components/stories/tab.rs @@ -0,0 +1,114 @@ +use std::cmp::Ordering; + +use gpui::{Div, Render}; +use story::Story; + +use crate::{prelude::*, TabPosition}; +use crate::{Indicator, Tab}; + +pub struct TabStory; + +impl Render for TabStory { + type Element = Div; + + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { + Story::container() + .child(Story::title_for::()) + .child(Story::label("Default")) + .child(h_stack().child(Tab::new("tab_1").child("Tab 1"))) + .child(Story::label("With indicator")) + .child( + h_stack().child( + Tab::new("tab_1") + .start_slot(Indicator::dot().color(Color::Warning)) + .child("Tab 1"), + ), + ) + .child(Story::label("With close button")) + .child( + h_stack().child( + Tab::new("tab_1") + .end_slot( + IconButton::new("close_button", Icon::Close) + .icon_color(Color::Muted) + .size(ButtonSize::None) + .icon_size(IconSize::XSmall), + ) + .child("Tab 1"), + ), + ) + .child(Story::label("List of tabs")) + .child( + h_stack() + .child(Tab::new("tab_1").child("Tab 1")) + .child(Tab::new("tab_2").child("Tab 2")), + ) + .child(Story::label("List of tabs with first tab selected")) + .child( + h_stack() + .child( + Tab::new("tab_1") + .selected(true) + .position(TabPosition::First) + .child("Tab 1"), + ) + .child( + Tab::new("tab_2") + .position(TabPosition::Middle(Ordering::Greater)) + .child("Tab 2"), + ) + .child( + Tab::new("tab_3") + .position(TabPosition::Middle(Ordering::Greater)) + .child("Tab 3"), + ) + .child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")), + ) + .child(Story::label("List of tabs with last tab selected")) + .child( + h_stack() + .child( + Tab::new("tab_1") + .position(TabPosition::First) + .child("Tab 1"), + ) + .child( + Tab::new("tab_2") + .position(TabPosition::Middle(Ordering::Less)) + .child("Tab 2"), + ) + .child( + Tab::new("tab_3") + .position(TabPosition::Middle(Ordering::Less)) + .child("Tab 3"), + ) + .child( + Tab::new("tab_4") + .position(TabPosition::Last) + .selected(true) + .child("Tab 4"), + ), + ) + .child(Story::label("List of tabs with second tab selected")) + .child( + h_stack() + .child( + Tab::new("tab_1") + .position(TabPosition::First) + .child("Tab 1"), + ) + .child( + Tab::new("tab_2") + .position(TabPosition::Middle(Ordering::Equal)) + .selected(true) + .child("Tab 2"), + ) + .child( + Tab::new("tab_3") + .position(TabPosition::Middle(Ordering::Greater)) + .child("Tab 3"), + ) + .child(Tab::new("tab_4").position(TabPosition::Last).child("Tab 4")), + ) + } +} diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a40a6ed0d321e4f28d9ff8f7ee4019da444cd34 --- /dev/null +++ b/crates/ui2/src/components/tab.rs @@ -0,0 +1,198 @@ +use std::cmp::Ordering; +use std::rc::Rc; + +use gpui::{AnyElement, AnyView, ClickEvent, IntoElement, MouseButton}; +use smallvec::SmallVec; + +use crate::prelude::*; + +/// The position of a [`Tab`] within a list of tabs. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum TabPosition { + /// The tab is first in the list. + First, + + /// The tab is in the middle of the list (i.e., it is not the first or last tab). + /// + /// The [`Ordering`] is where this tab is positioned with respect to the selected tab. + Middle(Ordering), + + /// The tab is last in the list. + Last, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum TabCloseSide { + Start, + End, +} + +#[derive(IntoElement)] +pub struct Tab { + id: ElementId, + selected: bool, + position: TabPosition, + close_side: TabCloseSide, + on_click: Option>, + tooltip: Option AnyView + 'static>>, + start_slot: Option, + end_slot: Option, + children: SmallVec<[AnyElement; 2]>, +} + +impl Tab { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + selected: false, + position: TabPosition::First, + close_side: TabCloseSide::End, + on_click: None, + tooltip: None, + start_slot: None, + end_slot: None, + children: SmallVec::new(), + } + } + + pub fn position(mut self, position: TabPosition) -> Self { + self.position = position; + self + } + + pub fn close_side(mut self, close_side: TabCloseSide) -> Self { + self.close_side = close_side; + self + } + + pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { + self.on_click = Some(Rc::new(handler)); + self + } + + pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { + self.tooltip = Some(Box::new(tooltip)); + self + } + + pub fn start_slot(mut self, element: impl Into>) -> Self { + self.start_slot = element.into().map(IntoElement::into_any_element); + self + } + + pub fn end_slot(mut self, element: impl Into>) -> Self { + self.end_slot = element.into().map(IntoElement::into_any_element); + self + } +} + +impl Selectable for Tab { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } +} + +impl ParentElement for Tab { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for Tab { + type Rendered = Div; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered { + const HEIGHT_IN_REMS: f32 = 30. / 16.; + + let (text_color, tab_bg, _tab_hover_bg, _tab_active_bg) = match self.selected { + false => ( + cx.theme().colors().text_muted, + cx.theme().colors().tab_inactive_background, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ), + true => ( + cx.theme().colors().text, + cx.theme().colors().tab_active_background, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, + ), + }; + + div() + .h(rems(HEIGHT_IN_REMS)) + .bg(tab_bg) + .border_color(cx.theme().colors().border) + .map(|this| match self.position { + TabPosition::First => { + if self.selected { + this.pl_px().border_r().pb_px() + } else { + this.pl_px().pr_px().border_b() + } + } + TabPosition::Last => { + if self.selected { + this.border_l().border_r().pb_px() + } else { + this.pr_px().pl_px().border_b() + } + } + TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(), + TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(), + TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(), + }) + .child( + h_stack() + .group("") + .id(self.id) + .relative() + .h_full() + .px_5() + .gap_1() + .text_color(text_color) + // .hover(|style| style.bg(tab_hover_bg)) + // .active(|style| style.bg(tab_active_bg)) + .when_some(self.on_click, |tab, on_click| { + tab.cursor_pointer().on_click(move |event, cx| { + // HACK: GPUI currently fires `on_click` with any mouse button, + // but we only care about the left button. + if event.down.button == MouseButton::Left { + (on_click)(event, cx) + } + }) + }) + .when_some(self.tooltip, |tab, tooltip| { + tab.tooltip(move |cx| tooltip(cx)) + }) + .child( + h_stack() + .w_3() + .h_3() + .justify_center() + .absolute() + .map(|this| match self.close_side { + TabCloseSide::Start => this.right_1(), + TabCloseSide::End => this.left_1(), + }) + .children(self.start_slot), + ) + .child( + h_stack() + .invisible() + .w_3() + .h_3() + .justify_center() + .absolute() + .map(|this| match self.close_side { + TabCloseSide::Start => this.left_1(), + TabCloseSide::End => this.right_1(), + }) + .group_hover("", |style| style.visible()) + .children(self.end_slot), + ) + .children(self.children), + ) + } +} diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 495819f6081745aaa243b4704e46ca84f10d8bda..f28759a7337c237aaa7958b1cdb18697882ef501 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -28,10 +28,10 @@ use std::{ use ui::{ h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, - Indicator, Label, Tooltip, + Indicator, Label, Tab, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; -use util::truncate_and_remove_front; +use util::{maybe, truncate_and_remove_front}; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -1438,42 +1438,49 @@ impl Pane { let is_active = ix == self.active_item_index; - let indicator = { + let indicator = maybe!({ let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) { - (true, _) => Some(Color::Warning), - (_, true) => Some(Color::Accent), - (false, false) => None, + (true, _) => Color::Warning, + (_, true) => Color::Accent, + (false, false) => return None, }; - h_stack() - .w_3() - .h_3() - .justify_center() - .absolute() - .map(|this| match close_side { - ClosePosition::Left => this.right_1(), - ClosePosition::Right => this.left_1(), + Some(Indicator::dot().color(indicator_color)) + }); + + let id = item.item_id(); + + let is_first_item = ix == 0; + let is_last_item = ix == self.items.len() - 1; + let position_relative_to_active_item = ix.cmp(&self.active_item_index); + + let tab = + Tab::new(ix) + .position(if is_first_item { + TabPosition::First + } else if is_last_item { + TabPosition::Last + } else { + TabPosition::Middle(position_relative_to_active_item) }) - .when_some(indicator_color, |this, indicator_color| { - this.child(Indicator::dot().color(indicator_color)) + .close_side(match close_side { + ClosePosition::Left => ui::TabCloseSide::Start, + ClosePosition::Right => ui::TabCloseSide::End, }) - }; - - let close_button = { - let id = item.item_id(); - - h_stack() - .invisible() - .w_3() - .h_3() - .justify_center() - .absolute() - .map(|this| match close_side { - ClosePosition::Left => this.left_1(), - ClosePosition::Right => this.right_1(), + .selected(ix == self.active_item_index()) + .on_click(cx.listener(move |pane: &mut Self, event, cx| { + pane.activate_item(ix, true, true, cx) + })) + // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) + // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) + // .on_drop(|_view, state: View, cx| { + // eprintln!("{:?}", state.read(cx)); + // }) + .when_some(item.tab_tooltip_text(cx), |tab, text| { + tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) }) - .group_hover("", |style| style.visible()) - .child( + .start_slot::(indicator) + .end_slot( // TODO: Fix button size IconButton::new("close tab", Icon::Close) .icon_color(Color::Muted) @@ -1484,67 +1491,7 @@ impl Pane { .detach_and_log_err(cx); })), ) - }; - - let tab = div() - .border_color(cx.theme().colors().border) - .bg(tab_bg) - // 30px @ 16px/rem - .h(rems(1.875)) - .map(|this| { - let is_first_item = ix == 0; - let is_last_item = ix == self.items.len() - 1; - match ix.cmp(&self.active_item_index) { - cmp::Ordering::Less => { - if is_first_item { - this.pl_px().pr_px().border_b() - } else { - this.border_l().pr_px().border_b() - } - } - cmp::Ordering::Greater => { - if is_last_item { - this.pr_px().pl_px().border_b() - } else { - this.border_r().pl_px().border_b() - } - } - cmp::Ordering::Equal => { - if is_first_item { - this.pl_px().border_r().pb_px() - } else { - this.border_l().border_r().pb_px() - } - } - } - }) - .child( - h_stack() - .group("") - .id(ix) - .relative() - .h_full() - .cursor_pointer() - .when_some(item.tab_tooltip_text(cx), |div, text| { - div.tooltip(move |cx| cx.build_view(|cx| Tooltip::new(text.clone())).into()) - }) - .on_click( - cx.listener(move |v: &mut Self, e, cx| v.activate_item(ix, true, true, cx)), - ) - // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) - // .drag_over::(|d| d.bg(cx.theme().colors().element_drop_target)) - // .on_drop(|_view, state: View, cx| { - // eprintln!("{:?}", state.read(cx)); - // }) - .px_5() - // .hover(|h| h.bg(tab_hover_bg)) - // .active(|a| a.bg(tab_active_bg)) - .gap_1() - .text_color(text_color) - .child(indicator) - .child(close_button) - .child(label), - ); + .child(label); right_click_menu(ix).trigger(tab).menu(|cx| { ContextMenu::build(cx, |menu, cx| { From 7c19650a4065eb8343d5e578c560ffb2aaa8fe98 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 7 Dec 2023 12:37:07 -0500 Subject: [PATCH 20/20] Remove when_else Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- crates/feedback2/src/feedback_modal.rs | 22 +++++++++++++--------- crates/gpui2/src/element.rs | 18 ------------------ crates/ui2/src/components/keybinding.rs | 12 +++++++----- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/crates/feedback2/src/feedback_modal.rs b/crates/feedback2/src/feedback_modal.rs index 68fbcfb3a30f69e462bf98b323d189a3b8a9675d..583d31ce5d32f92236151c804f59f64bdbb6cf4a 100644 --- a/crates/feedback2/src/feedback_modal.rs +++ b/crates/feedback2/src/feedback_modal.rs @@ -319,10 +319,12 @@ impl Render for FeedbackModal { "Characters: {}", characters_remaining )) - .when_else( - valid_character_count, - |this| this.color(Color::Success), - |this| this.color(Color::Error) + .color( + if valid_character_count { + Color::Success + } else { + Color::Error + } ) ), ) @@ -349,11 +351,13 @@ impl Render for FeedbackModal { .color(Color::Muted) // TODO: replicate this logic when clicking outside the modal // TODO: Will require somehow overriding the modal dismal default behavior - .when_else( - has_feedback, - |this| this.on_click(dismiss_prompt), - |this| this.on_click(dismiss) - ) + .map(|this| { + if has_feedback { + this.on_click(dismiss_prompt) + } else { + this.on_click(dismiss) + } + }) ) .child( Button::new("send_feedback", submit_button_text) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index a67276b0bcd62c91aab801be3d61fc784a2717dd..226a477012a025474df2a58c70be56c10ef37fc3 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -69,24 +69,6 @@ pub trait IntoElement: Sized { self.map(|this| if condition { then(this) } else { this }) } - fn when_else( - self, - condition: bool, - then: impl FnOnce(Self) -> Self, - otherwise: impl FnOnce(Self) -> Self, - ) -> Self - where - Self: Sized, - { - self.map(|this| { - if condition { - then(this) - } else { - otherwise(this) - } - }) - } - fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self where Self: Sized, diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 29586fd1941ba1fe69608e14d6f7fb01e053d56c..8314f607fefd24a968fa2b5b099594985929736c 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -98,11 +98,13 @@ impl RenderOnce for Key { div() .py_0() - .when_else( - single_char, - |el| el.w(rems(14. / 16.)).flex().flex_none().justify_center(), - |el| el.px_0p5(), - ) + .map(|this| { + if single_char { + this.w(rems(14. / 16.)).flex().flex_none().justify_center() + } else { + this.px_0p5() + } + }) .h(rems(14. / 16.)) .text_ui() .line_height(relative(1.))