diff --git a/Cargo.lock b/Cargo.lock index 805a9f4bf606a51f470862629d4124921175e6d7..4a6a42b1c396107f08c3250f8a16717be1b88cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2805,6 +2805,7 @@ dependencies = [ "tree-sitter-html", "tree-sitter-rust", "tree-sitter-typescript", + "ui2", "unindent", "util", "workspace2", diff --git a/crates/editor2/Cargo.toml b/crates/editor2/Cargo.toml index b897110966709ecef886caf8adef36f1973cc4bd..e45c33d91759f2e7d21283e45d1011cdd26abcb8 100644 --- a/crates/editor2/Cargo.toml +++ b/crates/editor2/Cargo.toml @@ -44,6 +44,7 @@ snippet = { path = "../snippet" } sum_tree = { path = "../sum_tree" } text = { package="text2", path = "../text2" } theme = { package="theme2", path = "../theme2" } +ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } sqlez = { path = "../sqlez" } workspace = { package = "workspace2", path = "../workspace2" } diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 51cd549923827ce21f9220e153f1f983bf5aa95d..5bc67a57b61feb5074852bb4a4b567c121a0f968 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -39,10 +39,12 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ - action, actions, point, px, relative, rems, size, AnyElement, AppContext, BackgroundExecutor, - Bounds, ClipboardItem, Context, DispatchContext, EventEmitter, FocusHandle, FontFeatures, - FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, Render, Subscription, - Task, TextStyle, View, ViewContext, VisualContext, WeakView, WindowContext, + action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext, + AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, + DispatchContext, EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, + HighlightStyle, Hsla, InputHandler, Model, MouseButton, ParentElement, Pixels, Render, + StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -67,7 +69,7 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock}; -use project::{FormatTrigger, Location, Project}; +use project::{FormatTrigger, Location, Project, ProjectTransaction}; use rand::prelude::*; use rpc::proto::*; use scroll::{ @@ -95,6 +97,7 @@ use text::{OffsetUtf16, Rope}; use theme::{ ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings, }; +use ui::IconButton; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace, @@ -384,26 +387,6 @@ actions!( UnfoldLines, ); -// impl_actions!( -// editor, -// [ -// SelectNext, -// SelectPrevious, -// SelectAllMatches, -// SelectToBeginningOfLine, -// SelectToEndOfLine, -// ToggleCodeActions, -// MovePageUp, -// MovePageDown, -// ConfirmCompletion, -// ConfirmCodeAction, -// ToggleComments, -// FoldAt, -// UnfoldAt, -// GutterHover -// ] -// ); - enum DocumentHighlightRead {} enum DocumentHighlightWrite {} enum InputComposition {} @@ -919,15 +902,14 @@ impl ContextMenu { fn render( &self, cursor_position: DisplayPoint, - style: EditorStyle, + style: &EditorStyle, workspace: Option>, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { - todo!() - // match self { - // ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), - // ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), - // } + match self { + ContextMenu::Completions(menu) => (cursor_position, menu.render(style, workspace, cx)), + ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), + } } } @@ -940,29 +922,13 @@ struct CompletionsMenu { match_candidates: Arc<[StringMatchCandidate]>, matches: Arc<[StringMatch]>, selected_item: usize, - list: UniformListState, -} - -// todo!(this is fake) -#[derive(Clone, Default)] -struct UniformListState; - -// todo!(this is fake) -impl UniformListState { - pub fn scroll_to(&mut self, target: ScrollTarget) {} -} - -// todo!(this is somewhat fake) -#[derive(Debug)] -pub enum ScrollTarget { - Show(usize), - Center(usize), + scroll_handle: UniformListScrollHandle, } impl CompletionsMenu { fn select_first(&mut self, project: Option<&Model>, cx: &mut ViewContext) { self.selected_item = 0; - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -973,7 +939,7 @@ impl CompletionsMenu { } else { self.selected_item = self.matches.len() - 1; } - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -984,14 +950,14 @@ impl CompletionsMenu { } else { self.selected_item = 0; } - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } fn select_last(&mut self, project: Option<&Model>, cx: &mut ViewContext) { self.selected_item = self.matches.len() - 1; - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); self.attempt_resolve_selected_completion_documentation(project, cx); cx.notify(); } @@ -1252,13 +1218,13 @@ impl CompletionsMenu { fn render( &self, - style: EditorStyle, + style: &EditorStyle, workspace: Option>, cx: &mut ViewContext, - ) { + ) -> AnyElement { todo!("old implementation below") } - // ) -> AnyElement { + // enum CompletionTag {} // let settings = EditorSettings>(cx); @@ -1527,14 +1493,14 @@ struct CodeActionsMenu { actions: Arc<[CodeAction]>, buffer: Model, selected_item: usize, - list: UniformListState, + scroll_handle: UniformListScrollHandle, deployed_from_indicator: bool, } impl CodeActionsMenu { fn select_first(&mut self, cx: &mut ViewContext) { self.selected_item = 0; - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); cx.notify() } @@ -1544,7 +1510,7 @@ impl CodeActionsMenu { } else { self.selected_item = self.actions.len() - 1; } - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); cx.notify(); } @@ -1554,13 +1520,13 @@ impl CodeActionsMenu { } else { self.selected_item = 0; } - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); cx.notify(); } fn select_last(&mut self, cx: &mut ViewContext) { self.selected_item = self.actions.len() - 1; - self.list.scroll_to(ScrollTarget::Show(self.selected_item)); + self.scroll_handle.scroll_to_item(self.selected_item); cx.notify() } @@ -1571,83 +1537,69 @@ impl CodeActionsMenu { fn render( &self, mut cursor_position: DisplayPoint, - style: EditorStyle, + style: &EditorStyle, cx: &mut ViewContext, ) -> (DisplayPoint, AnyElement) { - todo!("old version below") - } - // enum ActionTag {} + let actions = self.actions.clone(); + let selected_item = self.selected_item; + let element = uniform_list( + "code_actions_menu", + self.actions.len(), + move |editor, range, cx| { + actions[range.clone()] + .iter() + .enumerate() + .map(|(ix, action)| { + let item_ix = range.start + ix; + let selected = selected_item == item_ix; + let colors = cx.theme().colors(); + div() + .px_2() + .text_color(colors.text) + .when(selected, |style| { + style + .bg(colors.element_active) + .text_color(colors.text_accent) + }) + .hover(|style| { + style + .bg(colors.element_hover) + .text_color(colors.text_accent) + }) + .on_mouse_down(MouseButton::Left, move |editor: &mut Editor, _, cx| { + cx.stop_propagation(); + editor + .confirm_code_action( + &ConfirmCodeAction { + item_ix: Some(item_ix), + }, + cx, + ) + .map(|task| task.detach_and_log_err(cx)); + }) + .child(action.lsp_action.title.clone()) + }) + .collect() + }, + ) + .bg(cx.theme().colors().element_background) + .px_2() + .py_1() + .with_width_from_item( + self.actions + .iter() + .enumerate() + .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) + .map(|(ix, _)| ix), + ) + .render(); - // let container_style = style.autocomplete.container; - // let actions = self.actions.clone(); - // let selected_item = self.selected_item; - // let element = UniformList::new( - // self.list.clone(), - // actions.len(), - // cx, - // move |_, range, items, cx| { - // let start_ix = range.start; - // for (ix, action) in actions[range].iter().enumerate() { - // let item_ix = start_ix + ix; - // items.push( - // MouseEventHandler::new::(item_ix, cx, |state, _| { - // let item_style = if item_ix == selected_item { - // style.autocomplete.selected_item - // } else if state.hovered() { - // style.autocomplete.hovered_item - // } else { - // style.autocomplete.item - // }; - - // Text::new(action.lsp_action.title.clone(), style.text.clone()) - // .with_soft_wrap(false) - // .contained() - // .with_style(item_style) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_down(MouseButton::Left, move |_, this, cx| { - // let workspace = this - // .workspace - // .as_ref() - // .and_then(|(workspace, _)| workspace.upgrade(cx)); - // cx.window_context().defer(move |cx| { - // if let Some(workspace) = workspace { - // workspace.update(cx, |workspace, cx| { - // if let Some(task) = Editor::confirm_code_action( - // workspace, - // &ConfirmCodeAction { - // item_ix: Some(item_ix), - // }, - // cx, - // ) { - // task.detach_and_log_err(cx); - // } - // }); - // } - // }); - // }) - // .into_any(), - // ); - // } - // }, - // ) - // .with_width_from_item( - // self.actions - // .iter() - // .enumerate() - // .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) - // .map(|(ix, _)| ix), - // ) - // .contained() - // .with_style(container_style) - // .into_any(); - - // if self.deployed_from_indicator { - // *cursor_position.column_mut() = 0; - // } + if self.deployed_from_indicator { + *cursor_position.column_mut() = 0; + } - // (cursor_position, element) - // } + (cursor_position, element) + } } pub struct CopilotState { @@ -3660,7 +3612,7 @@ impl Editor { completions: Arc::new(RwLock::new(completions.into())), matches: Vec::new().into(), selected_item: 0, - list: Default::default(), + scroll_handle: UniformListScrollHandle::new(), }; menu.filter(query.as_deref(), cx.background_executor().clone()) .await; @@ -3846,156 +3798,161 @@ impl Editor { // })) // } - // pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { - // let mut context_menu = self.context_menu.write(); - // if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { - // *context_menu = None; - // cx.notify(); - // return; - // } - // drop(context_menu); - - // let deployed_from_indicator = action.deployed_from_indicator; - // let mut task = self.code_actions_task.take(); - // cx.spawn(|this, mut cx| async move { - // while let Some(prev_task) = task { - // prev_task.await; - // task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; - // } + pub fn toggle_code_actions(&mut self, action: &ToggleCodeActions, cx: &mut ViewContext) { + let mut context_menu = self.context_menu.write(); + if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) { + *context_menu = None; + cx.notify(); + return; + } + drop(context_menu); - // this.update(&mut cx, |this, cx| { - // if this.focused { - // if let Some((buffer, actions)) = this.available_code_actions.clone() { - // this.completion_tasks.clear(); - // this.discard_copilot_suggestion(cx); - // *this.context_menu.write() = - // Some(ContextMenu::CodeActions(CodeActionsMenu { - // buffer, - // actions, - // selected_item: Default::default(), - // list: Default::default(), - // deployed_from_indicator, - // })); - // } - // } - // })?; + let deployed_from_indicator = action.deployed_from_indicator; + let mut task = self.code_actions_task.take(); + cx.spawn(|this, mut cx| async move { + while let Some(prev_task) = task { + prev_task.await; + task = this.update(&mut cx, |this, _| this.code_actions_task.take())?; + } - // Ok::<_, anyhow::Error>(()) - // }) - // .detach_and_log_err(cx); - // } + this.update(&mut cx, |this, cx| { + if this.focus_handle.is_focused(cx) { + if let Some((buffer, actions)) = this.available_code_actions.clone() { + this.completion_tasks.clear(); + this.discard_copilot_suggestion(cx); + *this.context_menu.write() = + Some(ContextMenu::CodeActions(CodeActionsMenu { + buffer, + actions, + selected_item: Default::default(), + scroll_handle: UniformListScrollHandle::default(), + deployed_from_indicator, + })); + cx.notify(); + } + } + })?; - // pub fn confirm_code_action( - // workspace: &mut Workspace, - // action: &ConfirmCodeAction, - // cx: &mut ViewContext, - // ) -> Option>> { - // let editor = workspace.active_item(cx)?.act_as::(cx)?; - // let actions_menu = if let ContextMenu::CodeActions(menu) = - // editor.update(cx, |editor, cx| editor.hide_context_menu(cx))? - // { - // menu - // } else { - // return None; - // }; - // let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item); - // let action = actions_menu.actions.get(action_ix)?.clone(); - // let title = action.lsp_action.title.clone(); - // let buffer = actions_menu.buffer; + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + } - // let apply_code_actions = workspace.project().clone().update(cx, |project, cx| { - // project.apply_code_action(buffer, action, true, cx) - // }); - // let editor = editor.downgrade(); - // Some(cx.spawn(|workspace, cx| async move { - // let project_transaction = apply_code_actions.await?; - // Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await - // })) - // } + pub fn confirm_code_action( + &mut self, + action: &ConfirmCodeAction, + cx: &mut ViewContext, + ) -> Option>> { + let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? { + menu + } else { + return None; + }; + let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item); + let action = actions_menu.actions.get(action_ix)?.clone(); + let title = action.lsp_action.title.clone(); + let buffer = actions_menu.buffer; + let workspace = self.workspace()?; - // async fn open_project_transaction( - // this: &WeakViewHandle Result<()> { - // let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?; - - // let mut entries = transaction.0.into_iter().collect::>(); - // entries.sort_unstable_by_key(|(buffer, _)| { - // buffer.read_with(&cx, |buffer, _| buffer.file().map(|f| f.path().clone())) - // }); + let apply_code_actions = workspace + .read(cx) + .project() + .clone() + .update(cx, |project, cx| { + project.apply_code_action(buffer, action, true, cx) + }); + let workspace = workspace.downgrade(); + Some(cx.spawn(|editor, cx| async move { + let project_transaction = apply_code_actions.await?; + Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await + })) + } - // // If the project transaction's edits are all contained within this editor, then - // // avoid opening a new editor to display them. + async fn open_project_transaction( + this: &WeakView, + workspace: WeakView, + transaction: ProjectTransaction, + title: String, + mut cx: AsyncWindowContext, + ) -> Result<()> { + let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?; - // if let Some((buffer, transaction)) = entries.first() { - // if entries.len() == 1 { - // let excerpt = this.read_with(&cx, |editor, cx| { - // editor - // .buffer() - // .read(cx) - // .excerpt_containing(editor.selections.newest_anchor().head(), cx) - // })?; - // if let Some((_, excerpted_buffer, excerpt_range)) = excerpt { - // if excerpted_buffer == *buffer { - // let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| { - // let excerpt_range = excerpt_range.to_offset(buffer); - // buffer - // .edited_ranges_for_transaction::(transaction) - // .all(|range| { - // excerpt_range.start <= range.start - // && excerpt_range.end >= range.end - // }) - // }); - - // if all_edits_within_excerpt { - // return Ok(()); - // } - // } - // } - // } - // } else { - // return Ok(()); - // } + let mut entries = transaction.0.into_iter().collect::>(); + cx.update(|_, cx| { + entries.sort_unstable_by_key(|(buffer, _)| { + buffer.read(cx).file().map(|f| f.path().clone()) + }); + })?; + + // If the project transaction's edits are all contained within this editor, then + // avoid opening a new editor to display them. + + if let Some((buffer, transaction)) = entries.first() { + if entries.len() == 1 { + let excerpt = this.update(&mut cx, |editor, cx| { + editor + .buffer() + .read(cx) + .excerpt_containing(editor.selections.newest_anchor().head(), cx) + })?; + if let Some((_, excerpted_buffer, excerpt_range)) = excerpt { + if excerpted_buffer == *buffer { + let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| { + let excerpt_range = excerpt_range.to_offset(buffer); + buffer + .edited_ranges_for_transaction::(transaction) + .all(|range| { + excerpt_range.start <= range.start + && excerpt_range.end >= range.end + }) + })?; - // let mut ranges_to_highlight = Vec::new(); - // let excerpt_buffer = cx.build_model(|cx| { - // let mut multibuffer = MultiBuffer::new(replica_id).with_title(title); - // for (buffer_handle, transaction) in &entries { - // let buffer = buffer_handle.read(cx); - // ranges_to_highlight.extend( - // multibuffer.push_excerpts_with_context_lines( - // buffer_handle.clone(), - // buffer - // .edited_ranges_for_transaction::(transaction) - // .collect(), - // 1, - // cx, - // ), - // ); - // } - // multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx); - // multibuffer - // }); + if all_edits_within_excerpt { + return Ok(()); + } + } + } + } + } else { + return Ok(()); + } - // workspace.update(&mut cx, |workspace, cx| { - // let project = workspace.project().clone(); - // let editor = - // cx.add_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); - // workspace.add_item(Box::new(editor.clone()), cx); - // editor.update(cx, |editor, cx| { - // editor.highlight_background::( - // ranges_to_highlight, - // |theme| theme.editor.highlighted_line_background, - // cx, - // ); - // }); - // })?; + let mut ranges_to_highlight = Vec::new(); + let excerpt_buffer = cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(replica_id).with_title(title); + for (buffer_handle, transaction) in &entries { + let buffer = buffer_handle.read(cx); + ranges_to_highlight.extend( + multibuffer.push_excerpts_with_context_lines( + buffer_handle.clone(), + buffer + .edited_ranges_for_transaction::(transaction) + .collect(), + 1, + cx, + ), + ); + } + multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx); + multibuffer + })?; + + workspace.update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let editor = + cx.build_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx)); + workspace.add_item(Box::new(editor.clone()), cx); + editor.update(cx, |editor, cx| { + editor.highlight_background::( + ranges_to_highlight, + |theme| theme.editor_highlighted_line_background, + cx, + ); + }); + })?; - // Ok(()) - // } + Ok(()) + } fn refresh_code_actions(&mut self, cx: &mut ViewContext) -> Option<()> { let project = self.project.clone()?; @@ -4390,41 +4347,29 @@ impl Editor { self.discard_copilot_suggestion(cx); } - // pub fn render_code_actions_indicator( - // &self, - // style: &EditorStyle, - // is_active: bool, - // cx: &mut ViewContext, - // ) -> Option> { - // if self.available_code_actions.is_some() { - // enum CodeActions {} - // Some( - // MouseEventHandler::new::(0, cx, |state, _| { - // Svg::new("icons/bolt.svg").with_color( - // style - // .code_actions - // .indicator - // .in_state(is_active) - // .style_for(state) - // .color, - // ) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .with_padding(Padding::uniform(3.)) - // .on_down(MouseButton::Left, |_, this, cx| { - // this.toggle_code_actions( - // &ToggleCodeActions { - // deployed_from_indicator: true, - // }, - // cx, - // ); - // }) - // .into_any(), - // ) - // } else { - // None - // } - // } + pub fn render_code_actions_indicator( + &self, + style: &EditorStyle, + is_active: bool, + cx: &mut ViewContext, + ) -> Option> { + if self.available_code_actions.is_some() { + Some( + IconButton::new("code_actions_indicator", ui::Icon::Bolt) + .on_click(|editor: &mut Editor, cx| { + editor.toggle_code_actions( + &ToggleCodeActions { + deployed_from_indicator: true, + }, + cx, + ); + }) + .render(), + ) + } else { + None + } + } // pub fn render_fold_indicators( // &self, @@ -4491,29 +4436,27 @@ impl Editor { // } pub fn context_menu_visible(&self) -> bool { - false - // todo!("context menu") - // self.context_menu - // .read() - // .as_ref() - // .map_or(false, |menu| menu.visible()) + self.context_menu + .read() + .as_ref() + .map_or(false, |menu| menu.visible()) } - // pub fn render_context_menu( - // &self, - // cursor_position: DisplayPoint, - // style: EditorStyle, - // cx: &mut ViewContext, - // ) -> Option<(DisplayPoint, AnyElement)> { - // self.context_menu.read().as_ref().map(|menu| { - // menu.render( - // cursor_position, - // style, - // self.workspace.as_ref().map(|(w, _)| w.clone()), - // cx, - // ) - // }) - // } + pub fn render_context_menu( + &self, + cursor_position: DisplayPoint, + style: &EditorStyle, + cx: &mut ViewContext, + ) -> Option<(DisplayPoint, AnyElement)> { + self.context_menu.read().as_ref().map(|menu| { + menu.render( + cursor_position, + style, + self.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) + }) + } fn hide_context_menu(&mut self, cx: &mut ViewContext) -> Option { cx.notify(); @@ -5954,29 +5897,29 @@ impl Editor { }); } - // pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { - // if let Some(context_menu) = self.context_menu.write().as_mut() { - // context_menu.select_first(self.project.as_ref(), cx); - // } - // } + pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext) { + if let Some(context_menu) = self.context_menu.write().as_mut() { + context_menu.select_first(self.project.as_ref(), cx); + } + } - // pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { - // if let Some(context_menu) = self.context_menu.write().as_mut() { - // context_menu.select_prev(self.project.as_ref(), cx); - // } - // } + pub fn context_menu_prev(&mut self, _: &ContextMenuPrev, cx: &mut ViewContext) { + if let Some(context_menu) = self.context_menu.write().as_mut() { + context_menu.select_prev(self.project.as_ref(), cx); + } + } - // pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { - // if let Some(context_menu) = self.context_menu.write().as_mut() { - // context_menu.select_next(self.project.as_ref(), cx); - // } - // } + pub fn context_menu_next(&mut self, _: &ContextMenuNext, cx: &mut ViewContext) { + if let Some(context_menu) = self.context_menu.write().as_mut() { + context_menu.select_next(self.project.as_ref(), cx); + } + } - // pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { - // if let Some(context_menu) = self.context_menu.write().as_mut() { - // context_menu.select_last(self.project.as_ref(), cx); - // } - // } + pub fn context_menu_last(&mut self, _: &ContextMenuLast, cx: &mut ViewContext) { + if let Some(context_menu) = self.context_menu.write().as_mut() { + context_menu.select_last(self.project.as_ref(), cx); + } + } pub fn move_to_previous_word_start( &mut self, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 3e77a66936443aa872fc6f196fb71257bfede82f..67fcbaa4ba11acf260ffad4c29f7f9c217d1f727 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -15,7 +15,7 @@ use crate::{ use anyhow::Result; use collections::{BTreeMap, HashMap}; use gpui::{ - black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, + black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler, KeyDownEvent, KeyListener, KeyMatch, Line, LineLayout, Modifiers, MouseButton, @@ -447,7 +447,7 @@ impl EditorElement { fn paint_gutter( &mut self, bounds: Bounds, - layout: &LayoutState, + layout: &mut LayoutState, editor: &mut Editor, cx: &mut ViewContext, ) { @@ -495,14 +495,21 @@ impl EditorElement { // } // } - // todo!("code actions indicator") - // if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { - // let mut x = 0.; - // let mut y = *row as f32 * line_height - scroll_top; - // x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x) / 2.; - // y += (line_height - indicator.size().y) / 2.; - // indicator.paint(bounds.origin + point(x, y), visible_bounds, editor, cx); - // } + if let Some(indicator) = layout.code_actions_indicator.as_mut() { + let available_space = size( + AvailableSpace::MinContent, + AvailableSpace::Definite(line_height), + ); + let indicator_size = indicator.element.measure(available_space, editor, cx); + let mut x = Pixels::ZERO; + let mut y = indicator.row as f32 * line_height - scroll_top; + // Center indicator. + x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; + y += (line_height - indicator_size.height) / 2.; + indicator + .element + .draw(bounds.origin + point(x, y), available_space, editor, cx); + } } fn paint_diff_hunks( @@ -596,7 +603,7 @@ impl EditorElement { fn paint_text( &mut self, bounds: Bounds, - layout: &LayoutState, + layout: &mut LayoutState, editor: &mut Editor, cx: &mut ViewContext, ) { @@ -787,48 +794,46 @@ impl EditorElement { ) } - cx.stack(0, |cx| { + cx.with_z_index(0, |cx| { for cursor in cursors { cursor.paint(content_origin, cx); } }); - // cx.scene().push_layer(Some(bounds)); - - // cx.scene().pop_layer(); - // if let Some((position, context_menu)) = layout.context_menu.as_mut() { - // cx.scene().push_stacking_context(None, None); - // let cursor_row_layout = - // &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; - // let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; - // let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top; - // let mut list_origin = content_origin + point(x, y); - // let list_width = context_menu.size().x; - // let list_height = context_menu.size().y; - - // // Snap the right edge of the list to the right edge of the window if - // // its horizontal bounds overflow. - // if list_origin.x + list_width > cx.window_size().x { - // list_origin.set_x((cx.window_size().x - list_width).max(0.)); - // } - - // if list_origin.y + list_height > bounds.max_y { - // list_origin - // .set_y(list_origin.y - layout.position_map.line_height - list_height); - // } + if let Some((position, context_menu)) = layout.context_menu.as_mut() { + cx.with_z_index(1, |cx| { + let line_height = self.style.text.line_height_in_pixels(cx.rem_size()); + let available_space = size( + AvailableSpace::MinContent, + AvailableSpace::Definite( + (12. * line_height).min((bounds.size.height - line_height) / 2.), + ), + ); + let context_menu_size = context_menu.measure(available_space, editor, cx); + + let cursor_row_layout = &layout.position_map.line_layouts + [(position.row() - start_row) as usize] + .line; + let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; + let y = + (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top; + let mut list_origin = content_origin + point(x, y); + let list_width = context_menu_size.width; + let list_height = context_menu_size.height; + + // Snap the right edge of the list to the right edge of the window if + // its horizontal bounds overflow. + if list_origin.x + list_width > cx.viewport_size().width { + list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO); + } - // context_menu.paint( - // list_origin, - // Bounds::::from_points( - // gpui::Point::::zero(), - // point(f32::MAX, f32::MAX), - // ), // Let content bleed outside of editor - // editor, - // cx, - // ); + if list_origin.y + list_height > bounds.lower_right().y { + list_origin.y -= layout.position_map.line_height - list_height; + } - // cx.scene().pop_stacking_context(); - // } + context_menu.draw(list_origin, available_space, editor, cx); + }) + } // if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() { // cx.scene().push_stacking_context(None, None); @@ -1774,26 +1779,28 @@ impl EditorElement { snapshot = editor.snapshot(cx); } - // todo!("context menu") - // let mut context_menu = None; - // let mut code_actions_indicator = None; - // if let Some(newest_selection_head) = newest_selection_head { - // if (start_row..end_row).contains(&newest_selection_head.row()) { - // if editor.context_menu_visible() { - // context_menu = - // editor.render_context_menu(newest_selection_head, style.clone(), cx); - // } + let mut context_menu = None; + let mut code_actions_indicator = None; + if let Some(newest_selection_head) = newest_selection_head { + if (start_row..end_row).contains(&newest_selection_head.row()) { + if editor.context_menu_visible() { + context_menu = + editor.render_context_menu(newest_selection_head, &self.style, cx); + } - // let active = matches!( - // editor.context_menu.read().as_ref(), - // Some(crate::ContextMenu::CodeActions(_)) - // ); + let active = matches!( + editor.context_menu.read().as_ref(), + Some(crate::ContextMenu::CodeActions(_)) + ); - // code_actions_indicator = editor - // .render_code_actions_indicator(&style, active, cx) - // .map(|indicator| (newest_selection_head.row(), indicator)); - // } - // } + code_actions_indicator = editor + .render_code_actions_indicator(&style, active, cx) + .map(|element| CodeActionsIndicator { + row: newest_selection_head.row(), + element, + }); + } + } let visible_rows = start_row..start_row + line_layouts.len() as u32; // todo!("hover") @@ -1831,18 +1838,6 @@ impl EditorElement { // ); // } - // todo!("code actions") - // if let Some((_, indicator)) = code_actions_indicator.as_mut() { - // indicator.layout( - // SizeConstraint::strict_along( - // Axis::Vertical, - // line_height * style.code_actions.vertical_scale, - // ), - // editor, - // cx, - // ); - // } - // todo!("fold indicators") // for fold_indicator in fold_indicators.iter_mut() { // if let Some(indicator) = fold_indicator.as_mut() { @@ -1941,8 +1936,8 @@ impl EditorElement { display_hunks, // blocks, selections, - // context_menu, - // code_actions_indicator, + context_menu, + code_actions_indicator, // fold_indicators, tab_invisible, space_invisible, @@ -2493,7 +2488,7 @@ impl Element for EditorElement { element_state: &mut Self::ElementState, cx: &mut gpui::ViewContext, ) { - let layout = self.compute_layout(editor, cx, bounds); + let mut layout = self.compute_layout(editor, cx, bounds); let gutter_bounds = Bounds { origin: bounds.origin, size: layout.gutter_size, @@ -2503,21 +2498,24 @@ impl Element for EditorElement { size: layout.text_size, }; - cx.with_content_mask(ContentMask { bounds }, |cx| { - self.paint_mouse_listeners( - bounds, - gutter_bounds, - text_bounds, - &layout.position_map, - cx, - ); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &layout, editor, cx); - } - self.paint_text(text_bounds, &layout, editor, cx); - let input_handler = ElementInputHandler::new(bounds, cx); - cx.handle_input(&editor.focus_handle, input_handler); + // We call with_z_index to establish a new stacking context. + cx.with_z_index(0, |cx| { + cx.with_content_mask(ContentMask { bounds }, |cx| { + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout.position_map, + cx, + ); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, editor, cx); + } + self.paint_text(text_bounds, &mut layout, editor, cx); + let input_handler = ElementInputHandler::new(bounds, cx); + cx.handle_input(&editor.focus_handle, input_handler); + }); }); } } @@ -3143,14 +3141,19 @@ pub struct LayoutState { show_scrollbars: bool, is_singleton: bool, max_row: u32, - // context_menu: Option<(DisplayPoint, AnyElement)>, - // code_actions_indicator: Option<(u32, AnyElement)>, + context_menu: Option<(DisplayPoint, AnyElement)>, + code_actions_indicator: Option, // hover_popovers: Option<(DisplayPoint, Vec>)>, // fold_indicators: Vec>>, tab_invisible: Line, space_invisible: Line, } +struct CodeActionsIndicator { + row: u32, + element: AnyElement, +} + struct PositionMap { size: Size, line_height: Pixels, @@ -4123,7 +4126,7 @@ fn build_key_listeners( build_action_listener(Editor::unfold_at), build_action_listener(Editor::fold_selected_ranges), build_action_listener(Editor::show_completions), - // build_action_listener(Editor::toggle_code_actions), todo!() + build_action_listener(Editor::toggle_code_actions), // build_action_listener(Editor::open_excerpts), todo!() build_action_listener(Editor::toggle_soft_wrap), build_action_listener(Editor::toggle_inlay_hints), @@ -4139,13 +4142,21 @@ fn build_key_listeners( build_action_listener(Editor::restart_language_server), build_action_listener(Editor::show_character_palette), // build_action_listener(Editor::confirm_completion), todo!() - // build_action_listener(Editor::confirm_code_action), todo!() + build_action_listener(|editor, action, cx| { + editor + .confirm_code_action(action, cx) + .map(|task| task.detach_and_log_err(cx)); + }), // build_action_listener(Editor::rename), todo!() // build_action_listener(Editor::confirm_rename), todo!() // build_action_listener(Editor::find_all_references), todo!() build_action_listener(Editor::next_copilot_suggestion), build_action_listener(Editor::previous_copilot_suggestion), build_action_listener(Editor::copilot_suggest), + build_action_listener(Editor::context_menu_first), + build_action_listener(Editor::context_menu_prev), + build_action_listener(Editor::context_menu_next), + build_action_listener(Editor::context_menu_last), build_key_listener( move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| { if phase == DispatchPhase::Bubble { diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 8fdc17de07296d95def915f3a605f3988913eb2a..9ee9eaa7c335960f3e3c6974b0a8798c3d13f9c4 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -1,4 +1,6 @@ -use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, ViewContext}; +use crate::{ + AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext, +}; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; use std::{any::Any, mem}; @@ -61,6 +63,19 @@ trait ElementObject { fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext); fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId; fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext); + fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size; + fn draw( + &mut self, + origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ); } struct RenderedElement> { @@ -79,6 +94,11 @@ enum ElementRenderPhase { layout_id: LayoutId, frame_state: Option, }, + LayoutComputed { + layout_id: LayoutId, + available_space: Size, + frame_state: Option, + }, Painted, } @@ -135,7 +155,9 @@ where } } ElementRenderPhase::Start => panic!("must call initialize before layout"), - ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => { + ElementRenderPhase::LayoutRequested { .. } + | ElementRenderPhase::LayoutComputed { .. } + | ElementRenderPhase::Painted => { panic!("element rendered twice") } }; @@ -152,6 +174,11 @@ where ElementRenderPhase::LayoutRequested { layout_id, mut frame_state, + } + | ElementRenderPhase::LayoutComputed { + layout_id, + mut frame_state, + .. } => { let bounds = cx.layout_bounds(layout_id); if let Some(id) = self.element.id() { @@ -171,6 +198,65 @@ where _ => panic!("must call layout before paint"), }; } + + fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size { + if matches!(&self.phase, ElementRenderPhase::Start) { + self.initialize(view_state, cx); + } + + if matches!(&self.phase, ElementRenderPhase::Initialized { .. }) { + self.layout(view_state, cx); + } + + let layout_id = match &mut self.phase { + ElementRenderPhase::LayoutRequested { + layout_id, + frame_state, + } => { + cx.compute_layout(*layout_id, available_space); + let layout_id = *layout_id; + self.phase = ElementRenderPhase::LayoutComputed { + layout_id, + available_space, + frame_state: frame_state.take(), + }; + layout_id + } + ElementRenderPhase::LayoutComputed { + layout_id, + available_space: prev_available_space, + .. + } => { + if available_space != *prev_available_space { + cx.compute_layout(*layout_id, available_space); + *prev_available_space = available_space; + } + *layout_id + } + _ => panic!("cannot measure after painting"), + }; + + cx.layout_bounds(layout_id).size + } + + fn draw( + &mut self, + mut origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) { + self.measure(available_space, view_state, cx); + // Ignore the element offset when drawing this element, as the origin is already specified + // in absolute terms. + origin -= cx.element_offset(); + cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx)) + } } pub struct AnyElement(Box>); @@ -196,6 +282,27 @@ impl AnyElement { pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext) { self.0.paint(view_state, cx) } + + /// Initializes this element and performs layout within the given available space to determine its size. + pub fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size { + self.0.measure(available_space, view_state, cx) + } + + /// Initializes this element and performs layout in the available space, then paints it at the given origin. + pub fn draw( + &mut self, + origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) { + self.0.draw(origin, available_space, view_state, cx) + } } pub trait Component { diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index e258d3e7dc6dba8c7d7c625981b7ef340c1dc96c..5c5709d32e5a12e247726789a35dfd21806c6c7c 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -101,7 +101,12 @@ impl Element for Text { .map(|line| line.wrap_count() + 1) .sum::(); let size = Size { - width: lines.iter().map(|line| line.layout.width).max().unwrap(), + width: lines + .iter() + .map(|line| line.layout.width) + .max() + .unwrap() + .ceil(), height: line_height * line_count, }; diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index 2fe61f5909a0f6c2a07ab147c80962cee552ca17..6687559d1c811349a3f18a5585f2d2d4110eb16e 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -30,6 +30,7 @@ where id: id.clone(), style, item_count, + item_to_measure_index: 0, render_items: Box::new(move |view, visible_range, cx| { f(view, visible_range, cx) .into_iter() @@ -45,6 +46,7 @@ pub struct UniformList { id: ElementId, style: StyleRefinement, item_count: usize, + item_to_measure_index: usize, render_items: Box< dyn for<'a> Fn( &'a mut V, @@ -56,7 +58,7 @@ pub struct UniformList { scroll_handle: Option, } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct UniformListScrollHandle(Arc>>); #[derive(Clone, Debug)] @@ -112,7 +114,7 @@ impl Element for UniformList { cx: &mut ViewContext, ) -> Self::ElementState { element_state.unwrap_or_else(|| { - let item_size = self.measure_first_item(view_state, None, cx); + let item_size = self.measure_item(view_state, None, cx); UniformListState { interactive: InteractiveElementState::default(), item_size, @@ -158,7 +160,6 @@ impl Element for UniformList { cx: &mut ViewContext, ) { let style = self.computed_style(); - style.paint(bounds, cx); let border = style.border_widths.to_pixels(cx.rem_size()); let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); @@ -170,10 +171,12 @@ impl Element for UniformList { ); cx.with_z_index(style.z_index.unwrap_or(0), |cx| { + style.paint(bounds, cx); + let content_size; if self.item_count > 0 { let item_height = self - .measure_first_item(view_state, Some(padded_bounds.size.width), cx) + .measure_item(view_state, Some(padded_bounds.size.width), cx) .height; if let Some(scroll_handle) = self.scroll_handle.clone() { scroll_handle.0.lock().replace(ScrollHandleState { @@ -207,19 +210,13 @@ impl Element for UniformList { cx.with_z_index(1, |cx| { for (item, ix) in items.iter_mut().zip(visible_range) { - item.initialize(view_state, cx); - - let layout_id = item.layout(view_state, cx); - cx.compute_layout( - layout_id, - Size { - width: AvailableSpace::Definite(bounds.size.width), - height: AvailableSpace::Definite(item_height), - }, - ); - let offset = + let item_origin = padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset); - cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx)) + let available_space = size( + AvailableSpace::Definite(padded_bounds.size.width), + AvailableSpace::Definite(item_height), + ); + item.draw(item_origin, available_space, view_state, cx); } }); } else { @@ -245,27 +242,31 @@ impl Element for UniformList { } impl UniformList { - fn measure_first_item( + pub fn with_width_from_item(mut self, item_index: Option) -> Self { + self.item_to_measure_index = item_index.unwrap_or(0); + self + } + + fn measure_item( &self, view_state: &mut V, list_width: Option, cx: &mut ViewContext, ) -> Size { - let mut items = (self.render_items)(view_state, 0..1, cx); - debug_assert!(items.len() == 1); + if self.item_count == 0 { + return Size::default(); + } + + let item_ix = cmp::min(self.item_to_measure_index, self.item_count - 1); + let mut items = (self.render_items)(view_state, item_ix..item_ix + 1, cx); let mut item_to_measure = items.pop().unwrap(); - item_to_measure.initialize(view_state, cx); - let layout_id = item_to_measure.layout(view_state, cx); - cx.compute_layout( - layout_id, - Size { - width: list_width.map_or(AvailableSpace::MinContent, |width| { - AvailableSpace::Definite(width) - }), - height: AvailableSpace::MinContent, - }, + let available_space = size( + list_width.map_or(AvailableSpace::MinContent, |width| { + AvailableSpace::Definite(width) + }), + AvailableSpace::MinContent, ); - cx.layout_bounds(layout_id).size + item_to_measure.measure(available_space, view_state, cx) } pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self { diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index f290c6a81c1b147c78940465e6fa2ffb63c9964f..e07300951ec61429ba617927649406409e74b531 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -785,6 +785,10 @@ impl Pixels { Self(self.0.round()) } + pub fn ceil(&self) -> Self { + Self(self.0.ceil()) + } + pub fn scale(&self, factor: f32) -> ScaledPixels { ScaledPixels(self.0 * factor) } diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 9724179eed4735b7959f93865bd234bc3d246823..ea87f73872cd445ee37e530d973d5e0e054a76fd 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -1,5 +1,6 @@ use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style}; -use collections::HashMap; +use collections::{HashMap, HashSet}; +use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, @@ -12,6 +13,7 @@ pub struct TaffyLayoutEngine { taffy: Taffy, children_to_parents: HashMap, absolute_layout_bounds: HashMap>, + computed_layouts: HashSet, } static EXPECT_MESSAGE: &'static str = @@ -23,9 +25,17 @@ impl TaffyLayoutEngine { taffy: Taffy::new(), children_to_parents: HashMap::default(), absolute_layout_bounds: HashMap::default(), + computed_layouts: HashSet::default(), } } + pub fn clear(&mut self) { + self.taffy.clear(); + self.children_to_parents.clear(); + self.absolute_layout_bounds.clear(); + self.computed_layouts.clear(); + } + pub fn request_layout( &mut self, style: &Style, @@ -115,6 +125,7 @@ impl TaffyLayoutEngine { } pub fn compute_layout(&mut self, id: LayoutId, available_space: Size) { + // Leaving this here until we have a better instrumentation approach. // println!("Laying out {} children", self.count_all_children(id)?); // println!("Max layout depth: {}", self.max_depth(0, id)?); @@ -124,6 +135,22 @@ impl TaffyLayoutEngine { // println!("N{} --> N{}", u64::from(a), u64::from(b)); // } // println!(""); + // + + if !self.computed_layouts.insert(id) { + let mut stack = SmallVec::<[LayoutId; 64]>::new(); + stack.push(id); + while let Some(id) = stack.pop() { + self.absolute_layout_bounds.remove(&id); + stack.extend( + self.taffy + .children(id.into()) + .expect(EXPECT_MESSAGE) + .into_iter() + .map(Into::into), + ); + } + } // let started_at = std::time::Instant::now(); self.taffy @@ -397,7 +424,7 @@ where } } -#[derive(Copy, Clone, Default, Debug)] +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)] pub enum AvailableSpace { /// The amount of space available is the specified number of pixels Definite(Pixels), diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b020366ad02659a158128f73f3497f5d994fad8c..fd3890d644e7890334c2cdbe629ee661ec8bc0b4 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -206,7 +206,7 @@ pub struct Window { display_id: DisplayId, sprite_atlas: Arc, rem_size: Pixels, - content_size: Size, + viewport_size: Size, pub(crate) layout_engine: TaffyLayoutEngine, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, @@ -305,7 +305,7 @@ impl Window { display_id, sprite_atlas, rem_size: px(16.), - content_size, + viewport_size: content_size, layout_engine: TaffyLayoutEngine::new(), root_view: None, element_id_stack: GlobalElementId::default(), @@ -630,7 +630,7 @@ impl<'a> WindowContext<'a> { fn window_bounds_changed(&mut self) { self.window.scale_factor = self.window.platform_window.scale_factor(); - self.window.content_size = self.window.platform_window.content_size(); + self.window.viewport_size = self.window.platform_window.content_size(); self.window.bounds = self.window.platform_window.bounds(); self.window.display_id = self.window.platform_window.display().id(); self.window.dirty = true; @@ -645,6 +645,10 @@ impl<'a> WindowContext<'a> { self.window.bounds } + pub fn viewport_size(&self) -> Size { + self.window.viewport_size + } + pub fn is_window_active(&self) -> bool { self.window.active } @@ -738,7 +742,7 @@ 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 stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + 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); let result = f(self); self.window.current_frame.z_index_stack.pop(); @@ -1036,13 +1040,13 @@ impl<'a> WindowContext<'a> { self.start_frame(); - self.stack(0, |cx| { - let available_space = cx.window.content_size.map(Into::into); + self.with_z_index(0, |cx| { + let available_space = cx.window.viewport_size.map(Into::into); root_view.draw(available_space, cx); }); if let Some(active_drag) = self.app.active_drag.take() { - self.stack(1, |cx| { + self.with_z_index(1, |cx| { let offset = cx.mouse_position() - active_drag.cursor_offset; cx.with_element_offset(Some(offset), |cx| { let available_space = @@ -1052,7 +1056,7 @@ impl<'a> WindowContext<'a> { }); }); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { - self.stack(1, |cx| { + self.with_z_index(1, |cx| { cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); @@ -1101,6 +1105,8 @@ impl<'a> WindowContext<'a> { self.text_system().start_frame(); let window = &mut *self.window; + window.layout_engine.clear(); + mem::swap(&mut window.previous_frame, &mut window.current_frame); let frame = &mut window.current_frame; frame.element_states.clear(); @@ -1750,7 +1756,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .unwrap_or_else(|| ContentMask { bounds: Bounds { origin: Point::default(), - size: self.window().content_size, + size: self.window().viewport_size, }, }) } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index f0dc85b445c326f801d5579d5f4002690e617b75..91653ea8cdd0158d88294886b826c680b5cbf9b3 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -98,6 +98,7 @@ impl IconButton { if let Some(click_handler) = self.handlers.click.clone() { button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { + cx.stop_propagation(); click_handler(state, cx); }); }