Cargo.lock 🔗
@@ -2805,6 +2805,7 @@ dependencies = [
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript",
+ "ui2",
"unindent",
"util",
"workspace2",
Antonio Scandurra created
Release Notes:
- N/A
Cargo.lock | 1
crates/editor2/Cargo.toml | 1
crates/editor2/src/editor.rs | 643 +++++++++++-------------
crates/editor2/src/element.rs | 207 ++++---
crates/gpui2/src/element.rs | 111 ++++
crates/gpui2/src/elements/text.rs | 7
crates/gpui2/src/elements/uniform_list.rs | 61 +-
crates/gpui2/src/geometry.rs | 4
crates/gpui2/src/taffy.rs | 31 +
crates/gpui2/src/window.rs | 24
crates/ui2/src/components/icon_button.rs | 1
11 files changed, 599 insertions(+), 492 deletions(-)
@@ -2805,6 +2805,7 @@ dependencies = [
"tree-sitter-html",
"tree-sitter-rust",
"tree-sitter-typescript",
+ "ui2",
"unindent",
"util",
"workspace2",
@@ -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" }
@@ -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<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
- 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<Project>>, cx: &mut ViewContext<Editor>) {
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<Project>>, cx: &mut ViewContext<Editor>) {
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<WeakView<Workspace>>,
cx: &mut ViewContext<Editor>,
- ) {
+ ) -> AnyElement<Editor> {
todo!("old implementation below")
}
- // ) -> AnyElement<Editor> {
+
// enum CompletionTag {}
// let settings = EditorSettings>(cx);
@@ -1527,14 +1493,14 @@ struct CodeActionsMenu {
actions: Arc<[CodeAction]>,
buffer: Model<Buffer>,
selected_item: usize,
- list: UniformListState,
+ scroll_handle: UniformListScrollHandle,
deployed_from_indicator: bool,
}
impl CodeActionsMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
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<Editor>) {
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<Editor>,
) -> (DisplayPoint, AnyElement<Editor>) {
- 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::<ActionTag, _>(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<Self>) {
- // 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<Self>) {
+ 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<Workspace>,
- // ) -> Option<Task<Result<()>>> {
- // let editor = workspace.active_item(cx)?.act_as::<Editor>(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<Self>,
+ ) -> Option<Task<Result<()>>> {
+ 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<Editor
- // workspace: WeakViewHandle<Workspace
- // transaction: ProjectTransaction,
- // title: String,
- // mut cx: AsyncAppContext,
- // ) -> Result<()> {
- // let replica_id = this.read_with(&cx, |this, cx| this.replica_id(cx))?;
-
- // let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
- // 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<Editor>,
+ workspace: WeakView<Workspace>,
+ 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::<usize>(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::<Vec<_>>();
+ 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::<usize>(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::<usize>(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::<Self>(
- // 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::<usize>(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::<Self>(
+ ranges_to_highlight,
+ |theme| theme.editor_highlighted_line_background,
+ cx,
+ );
+ });
+ })?;
- // Ok(())
- // }
+ Ok(())
+ }
fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> 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<Self>,
- // ) -> Option<AnyElement<Self>> {
- // if self.available_code_actions.is_some() {
- // enum CodeActions {}
- // Some(
- // MouseEventHandler::new::<CodeActions, _>(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<Self>,
+ ) -> Option<AnyElement<Self>> {
+ 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<Editor>,
- // ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
- // 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<Editor>,
+ ) -> Option<(DisplayPoint, AnyElement<Editor>)> {
+ 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<Self>) -> Option<ContextMenu> {
cx.notify();
@@ -5954,29 +5897,29 @@ impl Editor {
});
}
- // pub fn context_menu_first(&mut self, _: &ContextMenuFirst, cx: &mut ViewContext<Self>) {
- // 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<Self>) {
+ 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<Self>) {
- // 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<Self>) {
+ 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<Self>) {
- // 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<Self>) {
+ 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<Self>) {
- // 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<Self>) {
+ 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,
@@ -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<Pixels>,
- layout: &LayoutState,
+ layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
@@ -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<Pixels>,
- layout: &LayoutState,
+ layout: &mut LayoutState,
editor: &mut Editor,
cx: &mut ViewContext<Editor>,
) {
@@ -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::<Pixels>::from_points(
- // gpui::Point::<Pixels>::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<Editor> for EditorElement {
element_state: &mut Self::ElementState,
cx: &mut gpui::ViewContext<Editor>,
) {
- 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<Editor> 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<Editor>)>,
- // code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
+ context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
+ code_actions_indicator: Option<CodeActionsIndicator>,
// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
// fold_indicators: Vec<Option<AnyElement<Editor>>>,
tab_invisible: Line,
space_invisible: Line,
}
+struct CodeActionsIndicator {
+ row: u32,
+ element: AnyElement<Editor>,
+}
+
struct PositionMap {
size: Size<Pixels>,
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 {
@@ -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<V> {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
+ fn measure(
+ &mut self,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Size<Pixels>;
+ fn draw(
+ &mut self,
+ origin: Point<Pixels>,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ );
}
struct RenderedElement<V: 'static, E: Element<V>> {
@@ -79,6 +94,11 @@ enum ElementRenderPhase<V> {
layout_id: LayoutId,
frame_state: Option<V>,
},
+ LayoutComputed {
+ layout_id: LayoutId,
+ available_space: Size<AvailableSpace>,
+ frame_state: Option<V>,
+ },
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<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Size<Pixels> {
+ 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<Pixels>,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ 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<V>(Box<dyn ElementObject<V>>);
@@ -196,6 +282,27 @@ impl<V> AnyElement<V> {
pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
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<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) -> Size<Pixels> {
+ 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<Pixels>,
+ available_space: Size<AvailableSpace>,
+ view_state: &mut V,
+ cx: &mut ViewContext<V>,
+ ) {
+ self.0.draw(origin, available_space, view_state, cx)
+ }
}
pub trait Component<V> {
@@ -101,7 +101,12 @@ impl<V: 'static> Element<V> for Text<V> {
.map(|line| line.wrap_count() + 1)
.sum::<usize>();
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,
};
@@ -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<V: 'static> {
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<V: 'static> {
scroll_handle: Option<UniformListScrollHandle>,
}
-#[derive(Clone)]
+#[derive(Clone, Default)]
pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
#[derive(Clone, Debug)]
@@ -112,7 +114,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
cx: &mut ViewContext<V>,
) -> 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<V: 'static> Element<V> for UniformList<V> {
cx: &mut ViewContext<V>,
) {
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<V: 'static> Element<V> for UniformList<V> {
);
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<V: 'static> Element<V> for UniformList<V> {
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<V: 'static> Element<V> for UniformList<V> {
}
impl<V> UniformList<V> {
- fn measure_first_item(
+ pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
+ self.item_to_measure_index = item_index.unwrap_or(0);
+ self
+ }
+
+ fn measure_item(
&self,
view_state: &mut V,
list_width: Option<Pixels>,
cx: &mut ViewContext<V>,
) -> Size<Pixels> {
- 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 {
@@ -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)
}
@@ -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<LayoutId, LayoutId>,
absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
+ computed_layouts: HashSet<LayoutId>,
}
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<AvailableSpace>) {
+ // 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),
@@ -206,7 +206,7 @@ pub struct Window {
display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
- content_size: Size<Pixels>,
+ viewport_size: Size<Pixels>,
pub(crate) layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView>,
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<Pixels> {
+ 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<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+ pub fn with_z_index<R>(&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<Window> + BorrowMut<AppContext> {
.unwrap_or_else(|| ContentMask {
bounds: Bounds {
origin: Point::default(),
- size: self.window().content_size,
+ size: self.window().viewport_size,
},
})
}
@@ -98,6 +98,7 @@ impl<V: 'static> IconButton<V> {
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);
});
}