diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e5a80e44f4..ae8335f242 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -592,11 +592,11 @@ impl ContextMenu { &self, cursor_position: DisplayPoint, style: EditorStyle, - cx: &AppContext, + cx: &mut RenderContext, ) -> (DisplayPoint, ElementBox) { match self { ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)), - ContextMenu::CodeActions(menu) => menu.render(cursor_position, style), + ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx), } } } @@ -633,54 +633,62 @@ impl CompletionsMenu { !self.matches.is_empty() } - fn render(&self, style: EditorStyle, _: &AppContext) -> ElementBox { + fn render(&self, style: EditorStyle, cx: &mut RenderContext) -> ElementBox { enum CompletionTag {} let completions = self.completions.clone(); let matches = self.matches.clone(); let selected_item = self.selected_item; let container_style = style.autocomplete.container; - UniformList::new(self.list.clone(), matches.len(), move |range, items, cx| { - let start_ix = range.start; - for (ix, mat) in matches[range].iter().enumerate() { - let completion = &completions[mat.candidate_id]; - let item_ix = start_ix + ix; - items.push( - MouseEventHandler::new::( - mat.candidate_id, - 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 - }; + UniformList::new( + self.list.clone(), + matches.len(), + cx, + move |_, range, items, cx| { + let start_ix = range.start; + for (ix, mat) in matches[range].iter().enumerate() { + let completion = &completions[mat.candidate_id]; + let item_ix = start_ix + ix; + items.push( + MouseEventHandler::new::( + mat.candidate_id, + 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(completion.label.text.clone(), style.text.clone()) - .with_soft_wrap(false) - .with_highlights(combine_syntax_and_fuzzy_match_highlights( - &completion.label.text, - style.text.color.into(), - styled_runs_for_code_label(&completion.label, &style.syntax), - &mat.positions, - )) - .contained() - .with_style(item_style) - .boxed() - }, - ) - .with_cursor_style(CursorStyle::PointingHand) - .on_mouse_down(move |cx| { - cx.dispatch_action(ConfirmCompletion { - item_ix: Some(item_ix), - }); - }) - .boxed(), - ); - } - }) + Text::new(completion.label.text.clone(), style.text.clone()) + .with_soft_wrap(false) + .with_highlights(combine_syntax_and_fuzzy_match_highlights( + &completion.label.text, + style.text.color.into(), + styled_runs_for_code_label( + &completion.label, + &style.syntax, + ), + &mat.positions, + )) + .contained() + .with_style(item_style) + .boxed() + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .on_mouse_down(move |cx| { + cx.dispatch_action(ConfirmCompletion { + item_ix: Some(item_ix), + }); + }) + .boxed(), + ); + } + }, + ) .with_width_from_item( self.matches .iter() @@ -772,14 +780,18 @@ impl CodeActionsMenu { &self, mut cursor_position: DisplayPoint, style: EditorStyle, + cx: &mut RenderContext, ) -> (DisplayPoint, ElementBox) { enum ActionTag {} 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(), move |range, items, cx| { + 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; @@ -808,17 +820,18 @@ impl CodeActionsMenu { .boxed(), ); } - }) - .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) - .boxed(); + }, + ) + .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) + .boxed(); if self.deployed_from_indicator { *cursor_position.column_mut() = 0; @@ -2578,7 +2591,7 @@ impl Editor { pub fn render_code_actions_indicator( &self, style: &EditorStyle, - cx: &mut ViewContext, + cx: &mut RenderContext, ) -> Option { if self.available_code_actions.is_some() { enum Tag {} @@ -2612,7 +2625,7 @@ impl Editor { &self, cursor_position: DisplayPoint, style: EditorStyle, - cx: &AppContext, + cx: &mut RenderContext, ) -> Option<(DisplayPoint, ElementBox)> { self.context_menu .as_ref() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 319ed21252..68751b000b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1024,8 +1024,6 @@ impl Element for EditorElement { max_row.saturating_sub(1) as f32, ); - let mut context_menu = None; - let mut code_actions_indicator = None; self.update_view(cx.app, |view, cx| { let clamped = view.clamp_scroll_left(scroll_max.x()); let autoscrolled; @@ -1045,7 +1043,11 @@ impl Element for EditorElement { if clamped || autoscrolled { snapshot = view.snapshot(cx); } + }); + let mut context_menu = None; + let mut code_actions_indicator = None; + cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| { let newest_selection_head = view .selections .newest::(cx) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ab4de50564..a0fca6aeb2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -468,6 +468,26 @@ impl TestAppContext { result } + pub fn render(&mut self, handle: &ViewHandle, f: F) -> T + where + F: FnOnce(&mut V, &mut RenderContext) -> T, + V: View, + { + handle.update(&mut *self.cx.borrow_mut(), |view, cx| { + let mut render_cx = RenderContext { + app: cx, + window_id: handle.window_id(), + view_id: handle.id(), + view_type: PhantomData, + titlebar_height: 0., + hovered_region_id: None, + clicked_region_id: None, + refreshing: false, + }; + f(view, &mut render_cx) + }) + } + pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext(self.cx.clone()) } @@ -1756,27 +1776,6 @@ impl MutableAppContext { ) } - pub fn build_render_context( - &mut self, - window_id: usize, - view_id: usize, - titlebar_height: f32, - hovered_region_id: Option, - clicked_region_id: Option, - refreshing: bool, - ) -> RenderContext { - RenderContext { - app: self, - window_id, - view_id, - view_type: PhantomData, - titlebar_height, - hovered_region_id, - clicked_region_id, - refreshing, - } - } - pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle where T: View, @@ -3429,13 +3428,13 @@ pub struct RenderParams { } pub struct RenderContext<'a, T: View> { + pub(crate) window_id: usize, + pub(crate) view_id: usize, + pub(crate) view_type: PhantomData, + pub(crate) hovered_region_id: Option, + pub(crate) clicked_region_id: Option, pub app: &'a mut MutableAppContext, - window_id: usize, - view_id: usize, - view_type: PhantomData, pub titlebar_height: f32, - hovered_region_id: Option, - clicked_region_id: Option, pub refreshing: bool, } @@ -3587,6 +3586,16 @@ impl UpgradeViewHandle for ViewContext<'_, V> { } } +impl UpgradeViewHandle for RenderContext<'_, V> { + fn upgrade_view_handle(&self, handle: &WeakViewHandle) -> Option> { + self.cx.upgrade_view_handle(handle) + } + + fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option { + self.cx.upgrade_any_view_handle(handle) + } +} + impl UpdateModel for ViewContext<'_, V> { fn update_model( &mut self, diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 3f384b5ea5..c320f2662e 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,7 +5,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{self, json}, - ElementBox, + ElementBox, RenderContext, View, }; use json::ToJson; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -41,27 +41,40 @@ pub struct LayoutState { items: Vec, } -pub struct UniformList -where - F: Fn(Range, &mut Vec, &mut LayoutContext), -{ +pub struct UniformList { state: UniformListState, item_count: usize, - append_items: F, + append_items: Box, &mut Vec, &mut LayoutContext) -> bool>, padding_top: f32, padding_bottom: f32, get_width_from_item: Option, } -impl UniformList -where - F: Fn(Range, &mut Vec, &mut LayoutContext), -{ - pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self { +impl UniformList { + pub fn new( + state: UniformListState, + item_count: usize, + cx: &mut RenderContext, + append_items: F, + ) -> Self + where + V: View, + F: 'static + Fn(&mut V, Range, &mut Vec, &mut RenderContext), + { + let handle = cx.handle(); Self { state, item_count, - append_items, + append_items: Box::new(move |range, items, cx| { + if let Some(handle) = handle.upgrade(cx) { + cx.render(&handle, |view, cx| { + append_items(view, range, items, cx); + }); + true + } else { + false + } + }), padding_top: 0., padding_bottom: 0., get_width_from_item: None, @@ -144,10 +157,7 @@ where } } -impl Element for UniformList -where - F: Fn(Range, &mut Vec, &mut LayoutContext), -{ +impl Element for UniformList { type LayoutState = LayoutState; type PaintState = (); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 899aa59309..a5b874188a 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -9,13 +9,14 @@ use crate::{ text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, ElementStateContext, Entity, FontSystem, ModelHandle, MouseRegion, MouseRegionId, ReadModel, - ReadView, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, - WeakModelHandle, WeakViewHandle, + ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, View, + ViewHandle, WeakModelHandle, WeakViewHandle, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use std::{ collections::{HashMap, HashSet}, + marker::PhantomData, ops::{Deref, DerefMut}, sync::Arc, }; @@ -172,12 +173,15 @@ impl Presenter { LayoutContext { rendered_views: &mut self.rendered_views, parents: &mut self.parents, - refreshing, font_cache: &self.font_cache, font_system: cx.platform().fonts(), text_layout_cache: &self.text_layout_cache, asset_cache: &self.asset_cache, view_stack: Vec::new(), + refreshing, + hovered_region_id: self.hovered_region_id, + clicked_region_id: self.clicked_region.as_ref().map(MouseRegion::id), + titlebar_height: self.titlebar_height, app: cx, } } @@ -342,12 +346,15 @@ pub struct LayoutContext<'a> { rendered_views: &'a mut HashMap, parents: &'a mut HashMap, view_stack: Vec, - pub refreshing: bool, pub font_cache: &'a Arc, pub font_system: Arc, pub text_layout_cache: &'a TextLayoutCache, pub asset_cache: &'a AssetCache, pub app: &'a mut MutableAppContext, + pub refreshing: bool, + titlebar_height: f32, + hovered_region_id: Option, + clicked_region_id: Option, } impl<'a> LayoutContext<'a> { @@ -362,6 +369,26 @@ impl<'a> LayoutContext<'a> { self.view_stack.pop(); size } + + pub fn render(&mut self, handle: &ViewHandle, f: F) -> T + where + F: FnOnce(&mut V, &mut RenderContext) -> T, + V: View, + { + handle.update(self.app, |view, cx| { + let mut render_cx = RenderContext { + app: cx, + window_id: handle.window_id(), + view_id: handle.id(), + view_type: PhantomData, + titlebar_height: self.titlebar_height, + hovered_region_id: self.hovered_region_id, + clicked_region_id: self.clicked_region_id, + refreshing: self.refreshing, + }; + f(view, &mut render_cx) + }) + } } impl<'a> Deref for LayoutContext<'a> { diff --git a/crates/gpui/src/views/select.rs b/crates/gpui/src/views/select.rs index d5d2105c3f..44576a0d95 100644 --- a/crates/gpui/src/views/select.rs +++ b/crates/gpui/src/views/select.rs @@ -123,7 +123,6 @@ impl View for Select { .boxed(), ); if self.is_open { - let handle = self.handle.clone(); result.add_child( Overlay::new( Container::new( @@ -131,9 +130,8 @@ impl View for Select { UniformList::new( self.list_state.clone(), self.item_count, - move |mut range, items, cx| { - let handle = handle.upgrade(cx).unwrap(); - let this = handle.read(cx); + cx, + move |this, mut range, items, cx| { let selected_item_ix = this.selected_item_ix; range.end = range.end.min(this.item_count); items.extend(range.map(|ix| { @@ -141,7 +139,7 @@ impl View for Select { ix, cx, |mouse_state, cx| { - (handle.read(cx).render_item)( + (this.render_item)( ix, if ix == selected_item_ix { ItemType::Selected diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 67db36208b..0dfd7c0a49 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -54,6 +54,7 @@ impl View for Picker { fn render(&mut self, cx: &mut RenderContext) -> gpui::ElementBox { let settings = cx.global::(); + let container_style = settings.theme.picker.container; let delegate = self.delegate.clone(); let match_count = if let Some(delegate) = delegate.upgrade(cx.app) { delegate.read(cx).match_count() @@ -80,8 +81,9 @@ impl View for Picker { UniformList::new( self.list_state.clone(), match_count, - move |mut range, items, cx| { - let delegate = delegate.upgrade(cx).unwrap(); + cx, + move |this, mut range, items, cx| { + let delegate = this.delegate.upgrade(cx).unwrap(); let selected_ix = delegate.read(cx).selected_index(); range.end = cmp::min(range.end, delegate.read(cx).match_count()); items.extend(range.map(move |ix| { @@ -103,7 +105,7 @@ impl View for Picker { .boxed(), ) .contained() - .with_style(settings.theme.picker.container) + .with_style(container_style) .constrained() .with_max_width(self.max_size.x()) .with_max_height(self.max_size.y()) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7056eb9ceb..1be1f1e940 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -9,8 +9,8 @@ use gpui::{ }, impl_internal_actions, keymap, platform::CursorStyle, - AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, PromptLevel, + RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use settings::Settings; @@ -706,8 +706,8 @@ impl ProjectPanel { fn for_each_visible_entry( &self, range: Range, - cx: &mut ViewContext, - mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext), + cx: &mut RenderContext, + mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext), ) { let mut ix = 0; for (worktree_id, visible_worktree_entries) in &self.visible_entries { @@ -780,7 +780,7 @@ impl ProjectPanel { details: EntryDetails, editor: &ViewHandle, theme: &theme::ProjectPanel, - cx: &mut ViewContext, + cx: &mut RenderContext, ) -> ElementBox { let kind = details.kind; let show_editor = details.is_editing && !details.is_processing; @@ -861,31 +861,28 @@ impl View for ProjectPanel { "ProjectPanel" } - fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { + fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> gpui::ElementBox { let theme = &cx.global::().theme.project_panel; let mut container_style = theme.container; let padding = std::mem::take(&mut container_style.padding); - let handle = self.handle.clone(); UniformList::new( self.list.clone(), self.visible_entries .iter() .map(|(_, worktree_entries)| worktree_entries.len()) .sum(), - move |range, items, cx| { + cx, + move |this, range, items, cx| { let theme = cx.global::().theme.clone(); - let this = handle.upgrade(cx).unwrap(); - this.update(cx.app, |this, cx| { - this.for_each_visible_entry(range.clone(), cx, |id, details, cx| { - items.push(Self::render_entry( - id, - details, - &this.filename_editor, - &theme.project_panel, - cx, - )); - }); - }) + this.for_each_visible_entry(range.clone(), cx, |id, details, cx| { + items.push(Self::render_entry( + id, + details, + &this.filename_editor, + &theme.project_panel, + cx, + )); + }); }, ) .with_padding_top(padding.top) @@ -1343,7 +1340,7 @@ mod tests { let mut result = Vec::new(); let mut project_entries = HashSet::new(); let mut has_editor = false; - panel.update(cx, |panel, cx| { + cx.render(panel, |panel, cx| { panel.for_each_visible_entry(range, cx, |project_entry, details, _| { if details.is_editing { assert!(!has_editor, "duplicate editor entry");