diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8504d3de5e..bf753a1784 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1584,27 +1584,34 @@ mod tests { } fn editor_blocks(editor: &View, cx: &mut WindowContext) -> Vec<(u32, SharedString)> { + let editor_view = editor.clone(); editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); snapshot .blocks_in_range(0..snapshot.max_point().row()) .enumerate() .filter_map(|(ix, (row, block))| { - let name = match block { - TransformBlock::Custom(block) => block - .render(&mut BlockContext { - view_context: cx, - anchor_x: px(0.), - gutter_padding: px(0.), - gutter_width: px(0.), - line_height: px(0.), - em_width: px(0.), - block_id: ix, - editor_style: &editor::EditorStyle::default(), - }) - .inner_id()? - .try_into() - .ok()?, + let name: SharedString = match block { + TransformBlock::Custom(block) => cx.with_element_context({ + let editor_view = editor_view.clone(); + |cx| -> Option { + block + .render(&mut BlockContext { + context: cx, + anchor_x: px(0.), + gutter_padding: px(0.), + gutter_width: px(0.), + line_height: px(0.), + em_width: px(0.), + block_id: ix, + view: editor_view, + editor_style: &editor::EditorStyle::default(), + }) + .inner_id()? + .try_into() + .ok() + } + })?, TransformBlock::ExcerptHeader { starts_new_buffer, .. diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index dbbcbccb6e..1b51e55352 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _}; use collections::{Bound, HashMap, HashSet}; -use gpui::{AnyElement, Pixels, ViewContext}; +use gpui::{AnyElement, ElementContext, Pixels, View}; use language::{BufferSnapshot, Chunk, Patch, Point}; use parking_lot::Mutex; use std::{ @@ -81,7 +81,8 @@ pub enum BlockStyle { } pub struct BlockContext<'a, 'b> { - pub view_context: &'b mut ViewContext<'a, Editor>, + pub context: &'b mut ElementContext<'a>, + pub view: View, pub anchor_x: Pixels, pub gutter_width: Pixels, pub gutter_padding: Pixels, @@ -933,16 +934,16 @@ impl BlockDisposition { } impl<'a> Deref for BlockContext<'a, '_> { - type Target = ViewContext<'a, Editor>; + type Target = ElementContext<'a>; fn deref(&self) -> &Self::Target { - self.view_context + self.context } } impl DerefMut for BlockContext<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { - self.view_context + self.context } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bb0fd93f50..b5b5a87ca5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3912,7 +3912,7 @@ impl Editor { gutter_hovered: bool, _line_height: Pixels, _gutter_margin: Pixels, - cx: &mut ViewContext, + editor_view: View, ) -> Vec> { fold_data .iter() @@ -3922,14 +3922,19 @@ impl Editor { .map(|(fold_status, buffer_row, active)| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { IconButton::new(ix as usize, ui::IconName::ChevronDown) - .on_click(cx.listener(move |editor, _e, cx| match fold_status { - FoldStatus::Folded => { - editor.unfold_at(&UnfoldAt { buffer_row }, cx); + .on_click({ + let view = editor_view.clone(); + move |_e, cx| { + view.update(cx, |editor, cx| match fold_status { + FoldStatus::Folded => { + editor.unfold_at(&UnfoldAt { buffer_row }, cx); + } + FoldStatus::Foldable => { + editor.fold_at(&FoldAt { buffer_row }, cx); + } + }) } - FoldStatus::Foldable => { - editor.fold_at(&FoldAt { buffer_row }, cx); - } - })) + }) .icon_color(ui::Color::Muted) .icon_size(ui::IconSize::Small) .selected(fold_status == FoldStatus::Folded) @@ -9575,10 +9580,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) .visible_on_hover(group_id) - .on_click(cx.listener({ + .on_click({ let message = diagnostic.message.clone(); - move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone())) - })) + move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone())) + }) .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)), ) .into_any_element() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ad166bb42b..ab92dac595 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -25,12 +25,12 @@ use collections::{BTreeMap, HashMap}; use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, - AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, - InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, - ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, - Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, + AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle, + DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds, + InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, + SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, + TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -330,7 +330,7 @@ impl EditorElement { register_action(view, cx, Editor::display_cursor_names); } - fn register_key_listeners(&self, cx: &mut WindowContext) { + fn register_key_listeners(&self, cx: &mut ElementContext) { cx.on_key_event({ let editor = self.editor.clone(); move |event: &ModifiersChangedEvent, phase, cx| { @@ -628,7 +628,7 @@ impl EditorElement { gutter_bounds: Bounds, text_bounds: Bounds, layout: &LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let bounds = gutter_bounds.union(&text_bounds); let scroll_top = @@ -711,7 +711,7 @@ impl EditorElement { &mut self, bounds: Bounds, layout: &mut LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let line_height = layout.position_map.line_height; @@ -782,7 +782,7 @@ impl EditorElement { }); } - fn paint_diff_hunks(bounds: Bounds, layout: &LayoutState, cx: &mut WindowContext) { + fn paint_diff_hunks(bounds: Bounds, layout: &LayoutState, cx: &mut ElementContext) { let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -886,7 +886,7 @@ impl EditorElement { &mut self, text_bounds: Bounds, layout: &mut LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let start_row = layout.visible_display_row_range.start; let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); @@ -1153,7 +1153,7 @@ impl EditorElement { &mut self, text_bounds: Bounds, layout: &mut LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); let start_row = layout.visible_display_row_range.start; @@ -1268,7 +1268,7 @@ impl EditorElement { &mut self, bounds: Bounds, layout: &mut LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { if layout.mode != EditorMode::Full { return; @@ -1512,7 +1512,7 @@ impl EditorElement { layout: &LayoutState, content_origin: gpui::Point, bounds: Bounds, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let start_row = layout.visible_display_row_range.start; let end_row = layout.visible_display_row_range.end; @@ -1564,7 +1564,7 @@ impl EditorElement { &mut self, bounds: Bounds, layout: &mut LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_left = scroll_position.x * layout.position_map.em_width; @@ -1814,7 +1814,7 @@ impl EditorElement { } } - fn compute_layout(&mut self, bounds: Bounds, cx: &mut WindowContext) -> LayoutState { + fn compute_layout(&mut self, bounds: Bounds, cx: &mut ElementContext) -> LayoutState { self.editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); let style = self.style.clone(); @@ -2083,7 +2083,9 @@ impl EditorElement { .width; let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width; - let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| { + let editor_view = cx.view().clone(); + let (scroll_width, blocks) = cx.with_element_context(|cx| { + cx.with_element_id(Some("editor_blocks"), |cx| { self.layout_blocks( start_row..end_row, &snapshot, @@ -2097,8 +2099,10 @@ impl EditorElement { &style, &line_layouts, editor, + editor_view, cx, ) + }) }); let scroll_max = point( @@ -2174,15 +2178,19 @@ impl EditorElement { cx, ); - let fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| { + let editor_view = cx.view().clone(); + let fold_indicators = cx.with_element_context(|cx| { + + cx.with_element_id(Some("gutter_fold_indicators"), |_cx| { editor.render_fold_indicators( fold_statuses, &style, editor.gutter_hovered, line_height, gutter_margin, - cx, + editor_view, ) + }) }); let invisible_symbol_font_size = font_size / 2.; @@ -2273,7 +2281,8 @@ impl EditorElement { style: &EditorStyle, line_layouts: &[LineWithInvisibles], editor: &mut Editor, - cx: &mut ViewContext, + editor_view: View, + cx: &mut ElementContext, ) -> (Pixels, Vec) { let mut block_id = 0; let (fixed_blocks, non_fixed_blocks) = snapshot @@ -2287,7 +2296,7 @@ impl EditorElement { available_space: Size, block_id: usize, editor: &mut Editor, - cx: &mut ViewContext| { + cx: &mut ElementContext| { let mut element = match block { TransformBlock::Custom(block) => { let align_to = block @@ -2306,13 +2315,14 @@ impl EditorElement { }; block.render(&mut BlockContext { - view_context: cx, + context: cx, anchor_x, gutter_padding, line_height, gutter_width, em_width, block_id, + view: editor_view.clone(), editor_style: &self.style, }) } @@ -2504,7 +2514,7 @@ impl EditorElement { &mut self, interactive_bounds: &InteractiveBounds, layout: &LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { cx.on_mouse_event({ let position_map = layout.position_map.clone(); @@ -2564,7 +2574,7 @@ impl EditorElement { gutter_bounds: Bounds, text_bounds: Bounds, layout: &LayoutState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let interactive_bounds = InteractiveBounds { bounds: bounds.intersect(&cx.content_mask().bounds), @@ -2787,7 +2797,7 @@ impl LineWithInvisibles { content_origin: gpui::Point, whitespace_setting: ShowWhitespaceSetting, selection_ranges: &[Range], - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let line_height = layout.position_map.line_height; let line_y = line_height * row as f32 - layout.position_map.scroll_position.y; @@ -2821,7 +2831,7 @@ impl LineWithInvisibles { row: u32, line_height: Pixels, whitespace_setting: ShowWhitespaceSetting, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let allowed_invisibles_regions = match whitespace_setting { ShowWhitespaceSetting::None => return, @@ -2870,7 +2880,7 @@ impl Element for EditorElement { fn request_layout( &mut self, _element_state: Option, - cx: &mut gpui::WindowContext, + cx: &mut gpui::ElementContext, ) -> (gpui::LayoutId, Self::State) { cx.with_view_id(self.editor.entity_id(), |cx| { self.editor.update(cx, |editor, cx| { @@ -2882,34 +2892,36 @@ impl Element for EditorElement { let mut style = Style::default(); style.size.width = relative(1.).into(); style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); - cx.request_layout(&style, None) + cx.with_element_context(|cx| cx.request_layout(&style, None)) } EditorMode::AutoHeight { max_lines } => { let editor_handle = cx.view().clone(); let max_line_number_width = self.max_line_number_width(&editor.snapshot(cx), cx); - cx.request_measured_layout( - Style::default(), - move |known_dimensions, _, cx| { - editor_handle - .update(cx, |editor, cx| { - compute_auto_height_layout( - editor, - max_lines, - max_line_number_width, - known_dimensions, - cx, - ) - }) - .unwrap_or_default() - }, - ) + cx.with_element_context(|cx| { + cx.request_measured_layout( + Style::default(), + move |known_dimensions, _, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + }) } EditorMode::Full => { let mut style = Style::default(); style.size.width = relative(1.).into(); style.size.height = relative(1.).into(); - cx.request_layout(&style, None) + cx.with_element_context(|cx| cx.request_layout(&style, None)) } }; @@ -2922,7 +2934,7 @@ impl Element for EditorElement { &mut self, bounds: Bounds, _element_state: &mut Self::State, - cx: &mut gpui::WindowContext, + cx: &mut gpui::ElementContext, ) { let editor = self.editor.clone(); @@ -3204,7 +3216,7 @@ impl Cursor { } } - pub fn paint(&self, origin: gpui::Point, cx: &mut WindowContext) { + pub fn paint(&self, origin: gpui::Point, cx: &mut ElementContext) { let bounds = match self.shape { CursorShape::Bar => Bounds { origin: self.origin + origin, @@ -3284,7 +3296,7 @@ pub struct HighlightedRangeLine { } impl HighlightedRange { - pub fn paint(&self, bounds: Bounds, cx: &mut WindowContext) { + pub fn paint(&self, bounds: Bounds, cx: &mut ElementContext) { if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx); self.paint_lines( @@ -3303,7 +3315,7 @@ impl HighlightedRange { start_y: Pixels, lines: &[HighlightedRangeLine], _bounds: Bounds, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { if lines.is_empty() { return; @@ -3521,14 +3533,16 @@ mod tests { .unwrap(); let state = cx .update_window(window.into(), |view, cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + cx.with_element_context(|cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) }) .unwrap(); @@ -3615,14 +3629,16 @@ mod tests { let state = cx .update_window(window.into(), |view, cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + cx.with_element_context(|cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) }) .unwrap(); @@ -3679,14 +3695,16 @@ mod tests { let mut element = EditorElement::new(&editor, style); let state = cx .update_window(window.into(), |view, cx| { - cx.with_view_id(view.entity_id(), |cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + cx.with_element_context(|cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) }) .unwrap(); @@ -3704,8 +3722,10 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx)) - .unwrap() + cx.update_window(window.into(), |_, cx| { + cx.with_element_context(|cx| element.paint(bounds, &mut (), cx)) + }) + .unwrap() } #[gpui::test] @@ -3880,13 +3900,15 @@ mod tests { .unwrap(); let layout_state = cx .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + cx.with_element_context(|cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 053897ee7f..4d588a668c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -640,8 +640,11 @@ impl<'a> VisualTestContext { .as_ref() .expect("Can't draw to this window without a root view") .entity_id(); - cx.with_view_id(entity_id, |cx| { - f(cx).draw(origin, space, cx); + + cx.with_element_context(|cx| { + cx.with_view_id(entity_id, |cx| { + f(cx).draw(origin, space, cx); + }) }); cx.refresh(); diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 01bb8ba6bf..cd5e6ea9dc 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -35,12 +35,12 @@ //! your own custom layout algorithm or rendering a code editor. use crate::{ - util::FluentBuilder, ArenaBox, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, + util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug}; +use std::{any::Any, fmt::Debug, ops::DerefMut}; /// Implemented by types that participate in laying out and painting the contents of a window. /// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy. @@ -56,12 +56,12 @@ pub trait Element: 'static + IntoElement { fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State); /// Once layout has been completed, this method will be called to paint the element to the screen. /// The state argument is the same state that was returned from [`Element::request_layout()`]. - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext); /// Convert this element into a dynamically-typed [`AnyElement`]. fn into_any(self) -> AnyElement { @@ -95,8 +95,8 @@ pub trait IntoElement: Sized { self, origin: Point, available_space: Size, - cx: &mut WindowContext, - f: impl FnOnce(&mut ::State, &mut WindowContext) -> R, + cx: &mut ElementContext, + f: impl FnOnce(&mut ::State, &mut ElementContext) -> R, ) -> R where T: Clone + Default + Debug + Into, @@ -193,14 +193,19 @@ impl Element for Component { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { - let mut element = self.0.take().unwrap().render(cx).into_any_element(); + let mut element = self + .0 + .take() + .unwrap() + .render(cx.deref_mut()) + .into_any_element(); let layout_id = element.request_layout(cx); (layout_id, element) } - fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut ElementContext) { element.paint(cx) } } @@ -224,21 +229,21 @@ pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>); trait ElementObject { fn element_id(&self) -> Option; - fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId; + fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId; - fn paint(&mut self, cx: &mut WindowContext); + fn paint(&mut self, cx: &mut ElementContext); fn measure( &mut self, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Size; fn draw( &mut self, origin: Point, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ); } @@ -276,7 +281,7 @@ impl DrawableElement { self.element.as_ref()?.element_id() } - fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { + fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id() { let layout_id = cx.with_element_state(id, |element_state, cx| { @@ -298,7 +303,7 @@ impl DrawableElement { layout_id } - fn paint(mut self, cx: &mut WindowContext) -> Option { + fn paint(mut self, cx: &mut ElementContext) -> Option { match self.phase { ElementDrawPhase::LayoutRequested { layout_id, @@ -343,7 +348,7 @@ impl DrawableElement { fn measure( &mut self, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Size { if matches!(&self.phase, ElementDrawPhase::Start) { self.request_layout(cx); @@ -384,7 +389,7 @@ impl DrawableElement { mut self, origin: Point, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Option { self.measure(available_space, cx); cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) @@ -400,18 +405,18 @@ where self.as_ref().unwrap().element_id() } - fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { + fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { DrawableElement::request_layout(self.as_mut().unwrap(), cx) } - fn paint(&mut self, cx: &mut WindowContext) { + fn paint(&mut self, cx: &mut ElementContext) { DrawableElement::paint(self.take().unwrap(), cx); } fn measure( &mut self, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Size { DrawableElement::measure(self.as_mut().unwrap(), available_space, cx) } @@ -420,7 +425,7 @@ where &mut self, origin: Point, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { DrawableElement::draw(self.take().unwrap(), origin, available_space, cx); } @@ -443,12 +448,12 @@ impl AnyElement { /// Request the layout ID of the element stored in this `AnyElement`. /// Used for laying out child elements in a parent element. - pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { + pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { self.0.request_layout(cx) } /// Paints the element stored in this `AnyElement`. - pub fn paint(&mut self, cx: &mut WindowContext) { + pub fn paint(&mut self, cx: &mut ElementContext) { self.0.paint(cx) } @@ -456,7 +461,7 @@ impl AnyElement { pub fn measure( &mut self, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Size { self.0.measure(available_space, cx) } @@ -466,7 +471,7 @@ impl AnyElement { &mut self, origin: Point, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { self.0.draw(origin, available_space, cx) } @@ -483,13 +488,13 @@ impl Element for AnyElement { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let layout_id = self.request_layout(cx); (layout_id, ()) } - fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut ElementContext) { self.paint(cx) } } @@ -531,7 +536,7 @@ impl Element for () { fn request_layout( &mut self, _state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { (cx.request_layout(&crate::Style::default(), None), ()) } @@ -540,7 +545,7 @@ impl Element for () { &mut self, _bounds: Bounds, _state: &mut Self::State, - _cx: &mut WindowContext, + _cx: &mut ElementContext, ) { } } diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 3cfd45b94d..8011f51e0c 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -1,10 +1,10 @@ use refineable::Refineable as _; -use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext}; +use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled}; /// Construct a canvas element with the given paint callback. /// Useful for adding short term custom drawing to a view. -pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContext)) -> Canvas { +pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut ElementContext)) -> Canvas { Canvas { paint_callback: Some(Box::new(callback)), style: StyleRefinement::default(), @@ -14,7 +14,7 @@ pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContex /// A canvas element, meant for accessing the low level paint API without defining a whole /// custom element pub struct Canvas { - paint_callback: Option, &mut WindowContext)>>, + paint_callback: Option, &mut ElementContext)>>, style: StyleRefinement, } @@ -36,7 +36,7 @@ impl Element for Canvas { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (crate::LayoutId, Self::State) { let mut style = Style::default(); style.refine(&self.style); @@ -44,7 +44,7 @@ impl Element for Canvas { (layout_id, style) } - fn paint(&mut self, bounds: Bounds, style: &mut Style, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, style: &mut Style, cx: &mut ElementContext) { style.paint(bounds, cx, |cx| { (self.paint_callback.take().unwrap())(&bounds, cx) }); diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 624630404c..9f2db136bd 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -23,12 +23,11 @@ //! use crate::{ - point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, - IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, - WindowContext, + point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds, ClickEvent, + DispatchPhase, Element, ElementContext, ElementId, FocusHandle, IntoElement, IsZero, + KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, + StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; @@ -41,6 +40,7 @@ use std::{ fmt::Debug, marker::PhantomData, mem, + ops::DerefMut, rc::Rc, time::Duration, }; @@ -1052,7 +1052,7 @@ impl Element for Div { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let mut child_layout_ids = SmallVec::new(); let (layout_id, interactive_state) = self.interactivity.layout( @@ -1082,7 +1082,7 @@ impl Element for Div { &mut self, bounds: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); @@ -1233,8 +1233,8 @@ impl Interactivity { pub fn layout( &mut self, element_state: Option, - cx: &mut WindowContext, - f: impl FnOnce(Style, &mut WindowContext) -> LayoutId, + cx: &mut ElementContext, + f: impl FnOnce(Style, &mut ElementContext) -> LayoutId, ) -> (LayoutId, InteractiveElementState) { let mut element_state = element_state.unwrap_or_default(); @@ -1281,8 +1281,8 @@ impl Interactivity { bounds: Bounds, content_size: Size, element_state: &mut InteractiveElementState, - cx: &mut WindowContext, - f: impl FnOnce(&Style, Point, &mut WindowContext), + cx: &mut ElementContext, + f: impl FnOnce(&Style, Point, &mut ElementContext), ) { let style = self.compute_style(Some(bounds), element_state, cx); let z_index = style.z_index.unwrap_or(0); @@ -1295,7 +1295,7 @@ impl Interactivity { .insert(debug_selector.clone(), bounds); } - let paint_hover_group_handler = |cx: &mut WindowContext| { + let paint_hover_group_handler = |cx: &mut ElementContext| { let hover_group_bounds = self .group_hover_style .as_ref() @@ -1319,7 +1319,7 @@ impl Interactivity { } cx.with_z_index(z_index, |cx| { - style.paint(bounds, cx, |cx| { + style.paint(bounds, cx, |cx: &mut ElementContext| { cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { #[cfg(debug_assertions)] @@ -1333,7 +1333,7 @@ impl Interactivity { let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); let str_len = element_id.len(); - let render_debug_text = |cx: &mut WindowContext| { + let render_debug_text = |cx: &mut ElementContext| { if let Some(text) = cx .text_system() .shape_text( @@ -1540,12 +1540,17 @@ impl Interactivity { let mut can_drop = true; if let Some(predicate) = &can_drop_predicate { - can_drop = - predicate(drag.value.as_ref(), cx); + can_drop = predicate( + drag.value.as_ref(), + cx.deref_mut(), + ); } if can_drop { - listener(drag.value.as_ref(), cx); + listener( + drag.value.as_ref(), + cx.deref_mut(), + ); cx.refresh(); cx.stop_propagation(); } @@ -1676,7 +1681,7 @@ impl Interactivity { *was_hovered = is_hovered; drop(was_hovered); - hover_listener(&is_hovered, cx); + hover_listener(&is_hovered, cx.deref_mut()); } }); } @@ -1897,7 +1902,7 @@ impl Interactivity { &self, bounds: Option>, element_state: &mut InteractiveElementState, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Style { let mut style = Style::default(); style.refine(&self.base_style); @@ -1921,7 +1926,9 @@ impl Interactivity { let mouse_position = cx.mouse_position(); if !cx.has_active_drag() { if let Some(group_hover) = self.group_hover_style.as_ref() { - if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) { + if let Some(group_bounds) = + GroupBounds::get(&group_hover.group, cx.deref_mut()) + { if group_bounds.contains(&mouse_position) && cx.was_top_layer(&mouse_position, cx.stacking_order()) { @@ -1944,13 +1951,13 @@ impl Interactivity { if let Some(drag) = cx.active_drag.take() { let mut can_drop = true; if let Some(can_drop_predicate) = &self.can_drop_predicate { - can_drop = can_drop_predicate(drag.value.as_ref(), cx); + can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut()); } if can_drop { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = - GroupBounds::get(&group_drag_style.group, cx) + GroupBounds::get(&group_drag_style.group, cx.deref_mut()) { if *state_type == drag.value.as_ref().type_id() && group_bounds.contains(&mouse_position) @@ -2096,12 +2103,12 @@ where fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { self.element.request_layout(state, cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { self.element.paint(bounds, state, cx) } } @@ -2171,12 +2178,12 @@ where fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { self.element.request_layout(state, cx) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { self.element.paint(bounds, state, cx) } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index e9a2e0676d..191cadf293 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use crate::{ - point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement, + point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size, - StyleRefinement, Styled, WindowContext, + StyleRefinement, Styled, }; use futures::FutureExt; use media::core_video::CVImageBuffer; @@ -81,7 +81,7 @@ impl Element for Img { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { self.interactivity .layout(element_state, cx, |style, cx| cx.request_layout(&style, [])) @@ -91,7 +91,7 @@ impl Element for Img { &mut self, bounds: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let source = self.source.clone(); self.interactivity.paint( diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index a3ed95ef33..c16fb32a52 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -7,9 +7,9 @@ //! If all of your elements are the same height, see [`UniformList`] for a simpler API use crate::{ - point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, - StyleRefinement, Styled, WindowContext, + point, px, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Element, + IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, + WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; @@ -359,7 +359,7 @@ impl Element for List { fn request_layout( &mut self, _state: Option, - cx: &mut crate::WindowContext, + cx: &mut crate::ElementContext, ) -> (crate::LayoutId, Self::State) { let mut style = Style::default(); style.refine(&self.style); @@ -373,7 +373,7 @@ impl Element for List { &mut self, bounds: Bounds, _state: &mut Self::State, - cx: &mut crate::WindowContext, + cx: &mut crate::ElementContext, ) { let state = &mut *self.state.0.borrow_mut(); diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 7840cfbd63..61c34bd938 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -2,8 +2,8 @@ use smallvec::SmallVec; use taffy::style::{Display, Position}; use crate::{ - point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, - Point, Size, Style, WindowContext, + point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement, + Pixels, Point, Size, Style, }; /// The state that the overlay element uses to track its children. @@ -74,7 +74,7 @@ impl Element for Overlay { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (crate::LayoutId, Self::State) { let child_layout_ids = self .children @@ -97,7 +97,7 @@ impl Element for Overlay { &mut self, bounds: crate::Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { if element_state.child_layout_ids.is_empty() { return; diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 370b4788f1..2ef0888563 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,6 +1,6 @@ use crate::{ - Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, - IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, + Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState, + Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, }; use util::ResultExt; @@ -32,7 +32,7 @@ impl Element for Svg { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { self.interactivity.layout(element_state, cx, |style, cx| { cx.request_layout(&style, None) @@ -43,7 +43,7 @@ impl Element for Svg { &mut self, bounds: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) where Self: Sized, { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index cf8b9477ce..13fc620076 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -1,7 +1,8 @@ use crate::{ - ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle, - IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, - SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY, + ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId, + HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, + TOOLTIP_DELAY, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -21,14 +22,14 @@ impl Element for &'static str { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let mut state = TextState::default(); let layout_id = state.layout(SharedString::from(*self), None, cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut ElementContext) { state.paint(bounds, self, cx) } } @@ -51,14 +52,14 @@ impl Element for SharedString { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let mut state = TextState::default(); let layout_id = state.layout(self.clone(), None, cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut ElementContext) { let text_str: &str = self.as_ref(); state.paint(bounds, text_str, cx) } @@ -130,14 +131,14 @@ impl Element for StyledText { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let mut state = TextState::default(); let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); (layout_id, state) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { state.paint(bounds, &self.text, cx) } } @@ -174,7 +175,7 @@ impl TextState { &mut self, text: SharedString, runs: Option>, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> LayoutId { let text_style = cx.text_style(); let font_size = text_style.font_size.to_pixels(cx.rem_size()); @@ -249,7 +250,7 @@ impl TextState { layout_id } - fn paint(&mut self, bounds: Bounds, text: &str, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, text: &str, cx: &mut ElementContext) { let element_state = self.lock(); let element_state = element_state .as_ref() @@ -377,7 +378,7 @@ impl Element for InteractiveText { fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { if let Some(InteractiveTextState { mouse_down_index, @@ -406,7 +407,7 @@ impl Element for InteractiveText { } } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { if let Some(click_listener) = self.click_listener.take() { let mouse_position = cx.mouse_position(); if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) { diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index e92d920666..108e669f75 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,7 +5,7 @@ //! elements with uniform height. use crate::{ - point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, + point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; @@ -110,7 +110,7 @@ impl Element for UniformList { fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let max_items = self.item_count; let item_size = state @@ -158,7 +158,7 @@ impl Element for UniformList { &mut self, bounds: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let style = self.interactivity @@ -280,7 +280,7 @@ impl UniformList { self } - fn measure_item(&self, list_width: Option, cx: &mut WindowContext) -> Size { + fn measure_item(&self, list_width: Option, cx: &mut ElementContext) -> Size { if self.item_count == 0 { return Size::default(); } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 954460e856..929e9ebfb4 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -220,11 +220,6 @@ pub trait EventEmitter: 'static {} /// A helper trait for auto-implementing certain methods on contexts that /// can be used interchangeably. pub trait BorrowAppContext { - /// Run a closure with a text style pushed onto the context. - fn with_text_style(&mut self, style: Option, f: F) -> R - where - F: FnOnce(&mut Self) -> R; - /// Set a global value on the context. fn set_global(&mut self, global: T); } @@ -233,20 +228,6 @@ impl BorrowAppContext for C where C: BorrowMut, { - fn with_text_style(&mut self, style: Option, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - if let Some(style) = style { - self.borrow_mut().push_text_style(style); - let result = f(self); - self.borrow_mut().pop_text_style(); - result - } else { - f(self) - } - } - fn set_global(&mut self, global: G) { self.borrow_mut().set_global(global) } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 146ba20867..08ada73d10 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -1,6 +1,6 @@ use crate::{ - Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap, - KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext, + Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding, + KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext, }; use collections::FxHashMap; use parking_lot::Mutex; @@ -36,7 +36,7 @@ pub(crate) struct DispatchNode { parent: Option, } -type KeyListener = Rc; +type KeyListener = Rc; #[derive(Clone)] pub(crate) struct DispatchActionListener { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 095233280e..c50cf34cfa 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -1,10 +1,10 @@ use std::{iter, mem, ops::Range}; use crate::{ - black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, - ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, - Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, - SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext, + black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, Bounds, ContentMask, Corners, + CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font, + FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, + SharedString, Size, SizeRefinement, Styled, TextRun, }; use collections::HashSet; use refineable::{Cascade, Refineable}; @@ -308,61 +308,12 @@ impl Style { } } - pub fn apply_text_style(&self, cx: &mut C, f: F) -> R - where - C: BorrowAppContext, - F: FnOnce(&mut C) -> R, - { - if self.text.is_some() { - cx.with_text_style(Some(self.text.clone()), f) - } else { - f(cx) - } - } - - /// Apply overflow to content mask - pub fn apply_overflow(&self, bounds: Bounds, cx: &mut C, f: F) -> R - where - C: BorrowWindow, - F: FnOnce(&mut C) -> R, - { - let current_mask = cx.content_mask(); - - let min = current_mask.bounds.origin; - let max = current_mask.bounds.lower_right(); - - let mask_bounds = match ( - self.overflow.x == Overflow::Visible, - self.overflow.y == Overflow::Visible, - ) { - // x and y both visible - (true, true) => return f(cx), - // x visible, y hidden - (true, false) => Bounds::from_corners( - point(min.x, bounds.origin.y), - point(max.x, bounds.lower_right().y), - ), - // x hidden, y visible - (false, true) => Bounds::from_corners( - point(bounds.origin.x, min.y), - point(bounds.lower_right().x, max.y), - ), - // both hidden - (false, false) => bounds, - }; - let mask = ContentMask { - bounds: mask_bounds, - }; - - cx.with_content_mask(Some(mask), f) - } - /// Paints the background of an element styled with this style. pub fn paint( &self, bounds: Bounds, - cx: &mut WindowContext, - continuation: impl FnOnce(&mut WindowContext), + cx: &mut ElementContext, + continuation: impl FnOnce(&mut ElementContext), ) { #[cfg(debug_assertions)] if self.debug_below { diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index a89fa30dfa..8013f5a1e0 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -1,6 +1,6 @@ use crate::{ - black, fill, point, px, size, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, - SharedString, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout, + black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result, + SharedString, UnderlineStyle, WrapBoundary, WrappedLineLayout, }; use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; @@ -33,7 +33,7 @@ impl ShapedLine { &self, origin: Point, line_height: Pixels, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Result<()> { paint_line( origin, @@ -66,7 +66,7 @@ impl WrappedLine { &self, origin: Point, line_height: Pixels, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> Result<()> { paint_line( origin, @@ -87,7 +87,7 @@ fn paint_line( line_height: Pixels, decoration_runs: &[DecorationRun], wrap_boundaries: &[WrapBoundary], - cx: &mut WindowContext<'_>, + cx: &mut ElementContext<'_>, ) -> Result<()> { let padding_top = (line_height - layout.ascent - layout.descent) / 2.; let baseline_offset = point(px(0.), padding_top + layout.ascent); diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index b6f6de7b6a..c6a4d4b52c 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,8 +1,8 @@ use crate::{ - seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, - Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, - IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle, - ViewContext, VisualContext, WeakModel, WindowContext, + seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds, + ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, + FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, + TextStyle, ViewContext, VisualContext, WeakModel, }; use anyhow::{Context, Result}; use std::{ @@ -94,7 +94,7 @@ impl Element for View { fn request_layout( &mut self, _state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { cx.with_view_id(self.entity_id(), |cx| { let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); @@ -103,7 +103,7 @@ impl Element for View { }) } - fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut ElementContext) { cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx)); } } @@ -202,7 +202,7 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement), cache: bool, } @@ -250,7 +250,7 @@ impl AnyView { &self, origin: Point, available_space: Size, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { cx.paint_view(self.entity_id(), |cx| { cx.with_absolute_element_offset(origin, |cx| { @@ -278,7 +278,7 @@ impl Element for AnyView { fn request_layout( &mut self, state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { cx.with_view_id(self.entity_id(), |cx| { if self.cache { @@ -299,7 +299,7 @@ impl Element for AnyView { }) } - fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut ElementContext) { cx.paint_view(self.entity_id(), |cx| { if !self.cache { state.element.take().unwrap().paint(cx); @@ -363,7 +363,7 @@ impl IntoElement for AnyView { /// A weak, dynamically-typed view handle that does not prevent the view from being released. pub struct AnyWeakView { model: AnyWeakModel, - layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement), } impl AnyWeakView { @@ -402,11 +402,11 @@ impl std::fmt::Debug for AnyWeakView { } mod any_view { - use crate::{AnyElement, AnyView, IntoElement, LayoutId, Render, WindowContext}; + use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render}; pub(crate) fn request_layout( view: &AnyView, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, AnyElement) { let view = view.clone().downcast::().unwrap(); let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element()); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9b3d04929b..2803d7c948 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,31 +1,26 @@ use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Arena, - AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, - DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, - ImageData, InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, - KeymatchResult, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, - MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, - PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, - PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, - Scene, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, Surface, - TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, + AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, + DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, + GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeymatchResult, KeystrokeEvent, + Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, + PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, + ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, + VisualContext, WeakView, WindowBounds, WindowOptions, }; use anyhow::{anyhow, Context as _, Result}; -use collections::{FxHashMap, FxHashSet}; +use collections::FxHashSet; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, StreamExt, }; -use media::core_video::CVImageBuffer; use parking_lot::RwLock; use slotmap::SlotMap; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, - borrow::{Borrow, BorrowMut, Cow}, + borrow::{Borrow, BorrowMut}, cell::RefCell, collections::hash_map::Entry, fmt::{Debug, Display}, @@ -40,7 +35,10 @@ use std::{ }, time::Duration, }; -use util::{post_inc, ResultExt}; +use util::ResultExt; + +mod element_cx; +pub use element_cx::*; const ACTIVE_DRAG_Z_INDEX: u8 = 1; @@ -51,7 +49,7 @@ pub struct StackingOrder { #[deref] #[deref_mut] context_stack: SmallVec<[u8; 64]>, - id: u32, + pub(crate) id: u32, } impl std::fmt::Debug for StackingOrder { @@ -99,7 +97,7 @@ impl DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyMouseListener = Box; + type AnyWindowFocusListener = Box bool + 'static>; struct FocusEvent { @@ -259,8 +257,8 @@ pub struct Window { pub(crate) platform_window: Box, display_id: DisplayId, sprite_atlas: Arc, - rem_size: Pixels, - viewport_size: Size, + pub(crate) rem_size: Pixels, + pub(crate) viewport_size: Size, layout_engine: Option, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, @@ -298,130 +296,10 @@ struct PendingInput { } pub(crate) struct ElementStateBox { - inner: Box, - parent_view_id: EntityId, + pub(crate) inner: Box, + pub(crate) parent_view_id: EntityId, #[cfg(debug_assertions)] - type_name: &'static str, -} - -struct RequestedInputHandler { - view_id: EntityId, - handler: Option, -} - -struct TooltipRequest { - view_id: EntityId, - tooltip: AnyTooltip, -} - -pub(crate) struct Frame { - focus: Option, - window_active: bool, - pub(crate) element_states: FxHashMap, - mouse_listeners: FxHashMap>, - pub(crate) dispatch_tree: DispatchTree, - pub(crate) scene: Scene, - pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, - pub(crate) z_index_stack: StackingOrder, - pub(crate) next_stacking_order_id: u32, - next_root_z_index: u8, - content_mask_stack: Vec>, - element_offset_stack: Vec>, - requested_input_handler: Option, - tooltip_request: Option, - cursor_styles: FxHashMap, - requested_cursor_style: Option, - pub(crate) view_stack: Vec, - pub(crate) reused_views: FxHashSet, - - #[cfg(any(test, feature = "test-support"))] - pub(crate) debug_bounds: collections::FxHashMap>, -} - -impl Frame { - fn new(dispatch_tree: DispatchTree) -> Self { - Frame { - focus: None, - window_active: false, - element_states: FxHashMap::default(), - mouse_listeners: FxHashMap::default(), - dispatch_tree, - scene: Scene::default(), - depth_map: Vec::new(), - z_index_stack: StackingOrder::default(), - next_stacking_order_id: 0, - next_root_z_index: 0, - content_mask_stack: Vec::new(), - element_offset_stack: Vec::new(), - requested_input_handler: None, - tooltip_request: None, - cursor_styles: FxHashMap::default(), - requested_cursor_style: None, - view_stack: Vec::new(), - reused_views: FxHashSet::default(), - - #[cfg(any(test, feature = "test-support"))] - debug_bounds: FxHashMap::default(), - } - } - - fn clear(&mut self) { - self.element_states.clear(); - self.mouse_listeners.values_mut().for_each(Vec::clear); - self.dispatch_tree.clear(); - self.depth_map.clear(); - self.next_stacking_order_id = 0; - self.next_root_z_index = 0; - self.reused_views.clear(); - self.scene.clear(); - self.requested_input_handler.take(); - self.tooltip_request.take(); - self.cursor_styles.clear(); - self.requested_cursor_style.take(); - debug_assert_eq!(self.view_stack.len(), 0); - } - - fn focus_path(&self) -> SmallVec<[FocusId; 8]> { - self.focus - .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) - .unwrap_or_default() - } - - fn finish(&mut self, prev_frame: &mut Self) { - // Reuse mouse listeners that didn't change since the last frame. - for (type_id, listeners) in &mut prev_frame.mouse_listeners { - let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); - for (order, view_id, listener) in listeners.drain(..) { - if self.reused_views.contains(&view_id) { - next_listeners.push((order, view_id, listener)); - } - } - } - - // Reuse entries in the depth map that didn't change since the last frame. - for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { - if self.reused_views.contains(&view_id) { - match self - .depth_map - .binary_search_by(|(level, _, _)| order.cmp(level)) - { - Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)), - } - } - } - - // Retain element states for views that didn't change since the last frame. - for (element_id, state) in prev_frame.element_states.drain() { - if self.reused_views.contains(&state.parent_view_id) { - self.element_states.entry(element_id).or_insert(state); - } - } - - // Reuse geometry that didn't change since the last frame. - self.scene - .reuse_views(&self.reused_views, &mut prev_frame.scene); - self.scene.finish(); - } + pub(crate) type_name: &'static str, } impl Window { @@ -815,80 +693,6 @@ impl<'a> WindowContext<'a> { result } - #[must_use] - /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which - /// layout is being requested, along with the layout ids of any children. This method is called during - /// calls to the `Element::layout` trait method and enables any element to participate in layout. - pub fn request_layout( - &mut self, - style: &Style, - children: impl IntoIterator, - ) -> LayoutId { - self.app.layout_id_buffer.clear(); - self.app.layout_id_buffer.extend(children); - let rem_size = self.rem_size(); - - self.window.layout_engine.as_mut().unwrap().request_layout( - style, - rem_size, - &self.app.layout_id_buffer, - ) - } - - /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children, - /// this variant takes a function that is invoked during layout so you can use arbitrary logic to - /// determine the element's size. One place this is used internally is when measuring text. - /// - /// The given closure is invoked at layout time with the known dimensions and available space and - /// returns a `Size`. - pub fn request_measured_layout< - F: FnMut(Size>, Size, &mut WindowContext) -> Size - + 'static, - >( - &mut self, - style: Style, - measure: F, - ) -> LayoutId { - let rem_size = self.rem_size(); - self.window - .layout_engine - .as_mut() - .unwrap() - .request_measured_layout(style, rem_size, measure) - } - - pub(crate) fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> { - self.window - .layout_engine - .as_ref() - .unwrap() - .requested_style(layout_id) - } - - /// Compute the layout for the given id within the given available space. - /// This method is called for its side effect, typically by the framework prior to painting. - /// After calling it, you can request the bounds of the given layout node id or any descendant. - pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { - let mut layout_engine = self.window.layout_engine.take().unwrap(); - layout_engine.compute_layout(layout_id, available_space, self); - self.window.layout_engine = Some(layout_engine); - } - - /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not - /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically - /// in order to pass your element its `Bounds` automatically. - pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Bounds { - let mut bounds = self - .window - .layout_engine - .as_mut() - .unwrap() - .layout_bounds(layout_id) - .map(Into::into); - bounds.origin += self.element_offset(); - bounds - } - fn window_bounds_changed(&mut self) { self.window.scale_factor = self.window.platform_window.scale_factor(); self.window.viewport_size = self.window.platform_window.content_size(); @@ -984,67 +788,6 @@ impl<'a> WindowContext<'a> { self.window.default_prevented } - /// Register a mouse event listener on the window for the next frame. The type of event - /// is determined by the first parameter of the given listener. When the next frame is rendered - /// the listener will be cleared. - pub fn on_mouse_event( - &mut self, - mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, - ) { - let view_id = self.parent_view_id(); - let order = self.window.next_frame.z_index_stack.clone(); - self.window - .next_frame - .mouse_listeners - .entry(TypeId::of::()) - .or_default() - .push(( - order, - view_id, - Box::new( - move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { - handler(event.downcast_ref().unwrap(), phase, cx) - }, - ), - )) - } - - /// Register a key event listener on the window for the next frame. The type of event - /// is determined by the first parameter of the given listener. When the next frame is rendered - /// the listener will be cleared. - /// - /// This is a fairly low-level method, so prefer using event handlers on elements unless you have - /// a specific need to register a global listener. - pub fn on_key_event( - &mut self, - listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, - ) { - self.window.next_frame.dispatch_tree.on_key_event(Rc::new( - move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { - if let Some(event) = event.downcast_ref::() { - listener(event, phase, cx) - } - }, - )); - } - - /// Register an action listener on the window for the next frame. The type of action - /// is determined by the first parameter of the given listener. When the next frame is rendered - /// the listener will be cleared. - /// - /// This is a fairly low-level method, so prefer using action handlers on elements unless you have - /// a specific need to register a global listener. - pub fn on_action( - &mut self, - action_type: TypeId, - listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, - ) { - self.window - .next_frame - .dispatch_tree - .on_action(action_type, Rc::new(listener)); - } - /// Determine whether the given action is available along the dispatch path to the currently focused element. pub fn is_action_available(&self, action: &dyn Action) -> bool { let target = self @@ -1072,29 +815,6 @@ impl<'a> WindowContext<'a> { self.window.modifiers } - /// Updates the cursor style at the platform level. - pub fn set_cursor_style(&mut self, style: CursorStyle) { - let view_id = self.parent_view_id(); - self.window.next_frame.cursor_styles.insert(view_id, style); - self.window.next_frame.requested_cursor_style = Some(style); - } - - /// Sets a tooltip to be rendered for the upcoming frame - pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { - let view_id = self.parent_view_id(); - self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); - } - - /// Called during painting to track which z-index is on top at each pixel position - pub fn add_opaque_layer(&mut self, bounds: Bounds) { - let stacking_order = self.window.next_frame.z_index_stack.clone(); - let view_id = self.parent_view_id(); - let depth_map = &mut self.window.next_frame.depth_map; - match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) { - Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)), - } - } - /// Returns true if there is no opaque layer containing the given point /// on top of the given level. Layers whose level is an extension of the /// level are not considered to be on top of the level. @@ -1136,371 +856,6 @@ impl<'a> WindowContext<'a> { &self.window.next_frame.z_index_stack } - /// Paint one or more drop shadows into the scene for the next frame at the current z-index. - pub fn paint_shadows( - &mut self, - bounds: Bounds, - corner_radii: Corners, - shadows: &[BoxShadow], - ) { - let scale_factor = self.scale_factor(); - let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - for shadow in shadows { - let mut shadow_bounds = bounds; - shadow_bounds.origin += shadow.offset; - shadow_bounds.dilate(shadow.spread_radius); - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Shadow { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: shadow_bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - corner_radii: corner_radii.scale(scale_factor), - color: shadow.color, - blur_radius: shadow.blur_radius.scale(scale_factor), - }, - ); - } - } - - /// Paint one or more quads into the scene for the next frame at the current stacking context. - /// Quads are colored rectangular regions with an optional background, border, and corner radius. - /// see [`fill`], [`outline`], and [`quad`] to construct this type. - pub fn paint_quad(&mut self, quad: PaintQuad) { - let scale_factor = self.scale_factor(); - let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Quad { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: quad.bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - background: quad.background, - border_color: quad.border_color, - corner_radii: quad.corner_radii.scale(scale_factor), - border_widths: quad.border_widths.scale(scale_factor), - }, - ); - } - - /// Paint the given `Path` into the scene for the next frame at the current z-index. - pub fn paint_path(&mut self, mut path: Path, color: impl Into) { - let scale_factor = self.scale_factor(); - let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - - path.content_mask = content_mask; - path.color = color.into(); - path.view_id = view_id.into(); - let window = &mut *self.window; - window - .next_frame - .scene - .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); - } - - /// Paint an underline into the scene for the next frame at the current z-index. - pub fn paint_underline( - &mut self, - origin: Point, - width: Pixels, - style: &UnderlineStyle, - ) { - let scale_factor = self.scale_factor(); - let height = if style.wavy { - style.thickness * 3. - } else { - style.thickness - }; - let bounds = Bounds { - origin, - size: size(width, height), - }; - let content_mask = self.content_mask(); - let view_id = self.parent_view_id(); - - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Underline { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds: bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - thickness: style.thickness.scale(scale_factor), - color: style.color.unwrap_or_default(), - wavy: style.wavy, - }, - ); - } - - /// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index. - /// The y component of the origin is the baseline of the glyph. - pub fn paint_glyph( - &mut self, - origin: Point, - font_id: FontId, - glyph_id: GlyphId, - font_size: Pixels, - color: Hsla, - ) -> Result<()> { - let scale_factor = self.scale_factor(); - let glyph_origin = origin.scale(scale_factor); - let subpixel_variant = Point { - x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, - y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, - }; - let params = RenderGlyphParams { - font_id, - glyph_id, - font_size, - subpixel_variant, - scale_factor, - is_emoji: false, - }; - - let raster_bounds = self.text_system().raster_bounds(¶ms)?; - if !raster_bounds.is_zero() { - let tile = - self.window - .sprite_atlas - .get_or_insert_with(¶ms.clone().into(), &mut || { - let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?; - Ok((size, Cow::Owned(bytes))) - })?; - let bounds = Bounds { - origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into), - size: tile.bounds.size.map(Into::into), - }; - let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - MonochromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - color, - tile, - }, - ); - } - Ok(()) - } - - /// Paint an emoji glyph into the scene for the next frame at the current z-index. - /// The y component of the origin is the baseline of the glyph. - pub fn paint_emoji( - &mut self, - origin: Point, - font_id: FontId, - glyph_id: GlyphId, - font_size: Pixels, - ) -> Result<()> { - let scale_factor = self.scale_factor(); - let glyph_origin = origin.scale(scale_factor); - let params = RenderGlyphParams { - font_id, - glyph_id, - font_size, - // We don't render emojis with subpixel variants. - subpixel_variant: Default::default(), - scale_factor, - is_emoji: true, - }; - - let raster_bounds = self.text_system().raster_bounds(¶ms)?; - if !raster_bounds.is_zero() { - let tile = - self.window - .sprite_atlas - .get_or_insert_with(¶ms.clone().into(), &mut || { - let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?; - Ok((size, Cow::Owned(bytes))) - })?; - let bounds = Bounds { - origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into), - size: tile.bounds.size.map(Into::into), - }; - let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - PolychromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - corner_radii: Default::default(), - content_mask, - tile, - grayscale: false, - }, - ); - } - Ok(()) - } - - /// Paint a monochrome SVG into the scene for the next frame at the current stacking context. - pub fn paint_svg( - &mut self, - bounds: Bounds, - path: SharedString, - color: Hsla, - ) -> Result<()> { - let scale_factor = self.scale_factor(); - let bounds = bounds.scale(scale_factor); - // Render the SVG at twice the size to get a higher quality result. - let params = RenderSvgParams { - path, - size: bounds - .size - .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)), - }; - - let tile = - self.window - .sprite_atlas - .get_or_insert_with(¶ms.clone().into(), &mut || { - let bytes = self.svg_renderer.render(¶ms)?; - Ok((params.size, Cow::Owned(bytes))) - })?; - let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - MonochromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - color, - tile, - }, - ); - - Ok(()) - } - - /// Paint an image into the scene for the next frame at the current z-index. - pub fn paint_image( - &mut self, - bounds: Bounds, - corner_radii: Corners, - data: Arc, - grayscale: bool, - ) -> Result<()> { - let scale_factor = self.scale_factor(); - let bounds = bounds.scale(scale_factor); - let params = RenderImageParams { image_id: data.id }; - - let tile = self - .window - .sprite_atlas - .get_or_insert_with(¶ms.clone().into(), &mut || { - Ok((data.size(), Cow::Borrowed(data.as_bytes()))) - })?; - let content_mask = self.content_mask().scale(scale_factor); - let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.parent_view_id(); - - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - PolychromeSprite { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - corner_radii, - tile, - grayscale, - }, - ); - Ok(()) - } - - /// Paint a surface into the scene for the next frame at the current z-index. - pub fn paint_surface(&mut self, bounds: Bounds, image_buffer: CVImageBuffer) { - let scale_factor = self.scale_factor(); - let bounds = bounds.scale(scale_factor); - let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id(); - let window = &mut *self.window; - window.next_frame.scene.insert( - &window.next_frame.z_index_stack, - Surface { - view_id: view_id.into(), - layer_id: 0, - order: 0, - bounds, - content_mask, - image_buffer, - }, - ); - } - - pub(crate) fn reuse_view(&mut self) { - let view_id = self.parent_view_id(); - let grafted_view_ids = self - .window - .next_frame - .dispatch_tree - .reuse_view(view_id, &mut self.window.rendered_frame.dispatch_tree); - for view_id in grafted_view_ids { - assert!(self.window.next_frame.reused_views.insert(view_id)); - - // Reuse the previous input handler requested during painting of the reused view. - if self - .window - .rendered_frame - .requested_input_handler - .as_ref() - .map_or(false, |requested| requested.view_id == view_id) - { - self.window.next_frame.requested_input_handler = - self.window.rendered_frame.requested_input_handler.take(); - } - - // Reuse the tooltip previously requested during painting of the reused view. - if self - .window - .rendered_frame - .tooltip_request - .as_ref() - .map_or(false, |requested| requested.view_id == view_id) - { - self.window.next_frame.tooltip_request = - self.window.rendered_frame.tooltip_request.take(); - } - - // Reuse the cursor styles previously requested during painting of the reused view. - if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { - self.window.next_frame.cursor_styles.insert(view_id, style); - self.window.next_frame.requested_cursor_style = Some(style); - } - } - } - /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { self.window.dirty = false; @@ -1513,44 +868,55 @@ impl<'a> WindowContext<'a> { if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() { - requested_handler.handler = self.window.platform_window.take_input_handler(); + let input_handler = self.window.platform_window.take_input_handler(); + requested_handler.handler = input_handler; } let root_view = self.window.root_view.take().unwrap(); - - self.with_z_index(0, |cx| { - cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { - for (action_type, action_listeners) in &cx.app.global_action_listeners { - for action_listener in action_listeners.iter().cloned() { - cx.window.next_frame.dispatch_tree.on_action( - *action_type, - Rc::new(move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { - action_listener(action, phase, cx) - }), - ) + self.with_element_context(|cx| { + cx.with_z_index(0, |cx| { + cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { + // We need to use cx.cx here so we can utilize borrow splitting + for (action_type, action_listeners) in &cx.cx.app.global_action_listeners { + for action_listener in action_listeners.iter().cloned() { + cx.cx.window.next_frame.dispatch_tree.on_action( + *action_type, + Rc::new( + move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { + action_listener(action, phase, cx) + }, + ), + ) + } } - } - let available_space = cx.window.viewport_size.map(Into::into); - root_view.draw(Point::default(), available_space, cx); + let available_space = cx.window.viewport_size.map(Into::into); + root_view.draw(Point::default(), available_space, cx); + }) }) }); if let Some(active_drag) = self.app.active_drag.take() { - self.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| { - let offset = cx.mouse_position() - active_drag.cursor_offset; - let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_drag.view.draw(offset, available_space, cx); + self.with_element_context(|cx| { + cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| { + let offset = cx.mouse_position() - active_drag.cursor_offset; + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + active_drag.view.draw(offset, available_space, cx); + }) }); self.active_drag = Some(active_drag); } else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() { - self.with_z_index(1, |cx| { - let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - tooltip_request.tooltip.view.draw( - tooltip_request.tooltip.cursor_offset, - available_space, - cx, - ); + self.with_element_context(|cx| { + cx.with_z_index(1, |cx| { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + tooltip_request.tooltip.view.draw( + tooltip_request.tooltip.cursor_offset, + available_space, + cx, + ); + }) }); self.window.next_frame.tooltip_request = Some(tooltip_request); } @@ -1741,7 +1107,9 @@ impl<'a> WindowContext<'a> { // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. for (_, _, handler) in &mut handlers { - handler(event, DispatchPhase::Capture, self); + self.with_element_context(|cx| { + handler(event, DispatchPhase::Capture, cx); + }); if !self.app.propagate_event { break; } @@ -1750,7 +1118,9 @@ impl<'a> WindowContext<'a> { // Bubble phase, where most normal handlers do their work. if self.app.propagate_event { for (_, _, handler) in handlers.iter_mut().rev() { - handler(event, DispatchPhase::Bubble, self); + self.with_element_context(|cx| { + handler(event, DispatchPhase::Bubble, cx); + }); if !self.app.propagate_event { break; } @@ -1866,7 +1236,9 @@ impl<'a> WindowContext<'a> { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Capture, self); + self.with_element_context(|cx| { + key_listener(event, DispatchPhase::Capture, cx); + }); if !self.propagate_event { return; } @@ -1878,7 +1250,9 @@ impl<'a> WindowContext<'a> { // Handle low level key events let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Bubble, self); + self.with_element_context(|cx| { + key_listener(event, DispatchPhase::Bubble, cx); + }); if !self.propagate_event { return; } @@ -1945,7 +1319,10 @@ impl<'a> WindowContext<'a> { { let any_action = action.as_any(); if action_type == any_action.type_id() { - listener(any_action, DispatchPhase::Capture, self); + self.with_element_context(|cx| { + listener(any_action, DispatchPhase::Capture, cx); + }); + if !self.propagate_event { return; } @@ -1963,7 +1340,11 @@ impl<'a> WindowContext<'a> { let any_action = action.as_any(); if action_type == any_action.type_id() { self.propagate_event = false; // Actions stop propagation by default during the bubble phase - listener(any_action, DispatchPhase::Bubble, self); + + self.with_element_context(|cx| { + listener(any_action, DispatchPhase::Bubble, cx); + }); + if !self.propagate_event { return; } @@ -2087,186 +1468,6 @@ impl<'a> WindowContext<'a> { } } - /// Invoke the given function with the given focus handle present on the key dispatch stack. - /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. - pub fn with_key_dispatch( - &mut self, - context: Option, - focus_handle: Option, - f: impl FnOnce(Option, &mut Self) -> R, - ) -> R { - let window = &mut self.window; - let focus_id = focus_handle.as_ref().map(|handle| handle.id); - window - .next_frame - .dispatch_tree - .push_node(context.clone(), focus_id, None); - - let result = f(focus_handle, self); - - self.window.next_frame.dispatch_tree.pop_node(); - - result - } - - /// Invoke the given function with the given view id present on the view stack. - /// This is a fairly low-level method used to layout views. - pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - let text_system = self.text_system().clone(); - text_system.with_view(view_id, || { - if self.window.next_frame.view_stack.last() == Some(&view_id) { - return f(self); - } else { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result - } - }) - } - - /// Invoke the given function with the given view id present on the view stack. - /// This is a fairly low-level method used to paint views. - pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - let text_system = self.text_system().clone(); - text_system.with_view(view_id, || { - if self.window.next_frame.view_stack.last() == Some(&view_id) { - return f(self); - } else { - self.window.next_frame.view_stack.push(view_id); - self.window - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(self); - self.window.next_frame.dispatch_tree.pop_node(); - self.window.next_frame.view_stack.pop(); - result - } - }) - } - - /// Updates or initializes state for an element with the given id that lives across multiple - /// frames. If an element with this ID existed in the rendered frame, its state will be passed - /// to the given closure. The state returned by the closure will be stored so it can be referenced - /// when drawing the next frame. - pub(crate) fn with_element_state( - &mut self, - id: ElementId, - f: impl FnOnce(Option, &mut Self) -> (R, S), - ) -> R - where - S: 'static, - { - self.with_element_id(Some(id), |cx| { - let global_id = cx.window().element_id_stack.clone(); - - if let Some(any) = cx - .window_mut() - .next_frame - .element_states - .remove(&global_id) - .or_else(|| { - cx.window_mut() - .rendered_frame - .element_states - .remove(&global_id) - }) - { - let ElementStateBox { - inner, - parent_view_id, - #[cfg(debug_assertions)] - type_name - } = any; - // Using the extra inner option to avoid needing to reallocate a new box. - let mut state_box = inner - .downcast::>() - .map_err(|_| { - #[cfg(debug_assertions)] - { - anyhow!( - "invalid element state type for id, requested_type {:?}, actual type: {:?}", - std::any::type_name::(), - type_name - ) - } - - #[cfg(not(debug_assertions))] - { - anyhow!( - "invalid element state type for id, requested_type {:?}", - std::any::type_name::(), - ) - } - }) - .unwrap(); - - // Actual: Option <- View - // Requested: () <- AnyElement - let state = state_box - .take() - .expect("element state is already on the stack"); - let (result, state) = f(Some(state), cx); - state_box.replace(state); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, ElementStateBox { - inner: state_box, - parent_view_id, - #[cfg(debug_assertions)] - type_name - }); - result - } else { - let (result, state) = f(None, cx); - let parent_view_id = cx.parent_view_id(); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, - ElementStateBox { - inner: Box::new(Some(state)), - parent_view_id, - #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } - - ); - result - } - }) - } - - fn parent_view_id(&self) -> EntityId { - *self - .window - .next_frame - .view_stack - .last() - .expect("a view should always be on the stack while drawing") - } - - /// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the - /// platform to receive textual input with proper integration with concerns such - /// as IME interactions. This handler will be active for the upcoming frame until the following frame is - /// rendered. - /// - /// [element_input_handler]: crate::ElementInputHandler - pub fn handle_input(&mut self, focus_handle: &FocusHandle, input_handler: impl InputHandler) { - if focus_handle.is_focused(self) { - let view_id = self.parent_view_id(); - self.window.next_frame.requested_input_handler = Some(RequestedInputHandler { - view_id, - handler: Some(PlatformInputHandler::new( - self.to_async(), - Box::new(input_handler), - )), - }) - } - } - /// Register a callback that can interrupt the closing of the current window based the returned boolean. /// If the callback returns false, the window won't be closed. pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { @@ -2285,6 +1486,32 @@ impl<'a> WindowContext<'a> { .unwrap_or(true) })) } + + pub(crate) fn parent_view_id(&self) -> EntityId { + *self + .window + .next_frame + .view_stack + .last() + .expect("a view should always be on the stack while drawing") + } + + /// Register an action listener on the window for the next frame. The type of action + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using action handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_action( + &mut self, + action_type: TypeId, + listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, + ) { + self.window + .next_frame + .dispatch_tree + .on_action(action_type, Rc::new(listener)); + } } impl Context for WindowContext<'_> { @@ -2474,149 +1701,6 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { fn window_mut(&mut self) -> &mut Window { self.borrow_mut() } - - /// Pushes the given element id onto the global stack and invokes the given closure - /// with a `GlobalElementId`, which disambiguates the given id in the context of its ancestor - /// ids. Because elements are discarded and recreated on each frame, the `GlobalElementId` is - /// used to associate state with identified elements across separate frames. - fn with_element_id( - &mut self, - id: Option>, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - if let Some(id) = id.map(Into::into) { - let window = self.window_mut(); - window.element_id_stack.push(id); - let result = f(self); - let window: &mut Window = self.borrow_mut(); - window.element_id_stack.pop(); - result - } else { - f(self) - } - } - - /// Invoke the given function with the given content mask after intersecting it - /// with the current mask. - fn with_content_mask( - &mut self, - mask: Option>, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - if let Some(mask) = mask { - let mask = mask.intersect(&self.content_mask()); - self.window_mut().next_frame.content_mask_stack.push(mask); - let result = f(self); - self.window_mut().next_frame.content_mask_stack.pop(); - result - } else { - f(self) - } - } - - /// Invoke the given function with the content mask reset to that - /// of the window. - fn break_content_mask(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { - let mask = ContentMask { - bounds: Bounds { - origin: Point::default(), - size: self.window().viewport_size, - }, - }; - let new_stacking_order_id = - post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); - let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); - self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; - self.window_mut() - .next_frame - .z_index_stack - .push(new_root_z_index); - self.window_mut().next_frame.content_mask_stack.push(mask); - let result = f(self); - self.window_mut().next_frame.content_mask_stack.pop(); - self.window_mut().next_frame.z_index_stack = old_stacking_order; - result - } - - /// 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`. - fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { - let new_stacking_order_id = - post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let old_stacking_order_id = mem::replace( - &mut self.window_mut().next_frame.z_index_stack.id, - new_stacking_order_id, - ); - self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; - self.window_mut().next_frame.z_index_stack.push(z_index); - let result = f(self); - self.window_mut().next_frame.z_index_stack.id = old_stacking_order_id; - self.window_mut().next_frame.z_index_stack.pop(); - result - } - - /// Updates the global element offset relative to the current offset. This is used to implement - /// scrolling. - fn with_element_offset( - &mut self, - offset: Point, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - if offset.is_zero() { - return f(self); - }; - - let abs_offset = self.element_offset() + offset; - self.with_absolute_element_offset(abs_offset, f) - } - - /// Updates the global element offset based on the given offset. This is used to implement - /// drag handles and other manual painting of elements. - fn with_absolute_element_offset( - &mut self, - offset: Point, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - self.window_mut() - .next_frame - .element_offset_stack - .push(offset); - let result = f(self); - self.window_mut().next_frame.element_offset_stack.pop(); - result - } - - /// Obtain the current element offset. - fn element_offset(&self) -> Point { - self.window() - .next_frame - .element_offset_stack - .last() - .copied() - .unwrap_or_default() - } - - /// Obtain the current content mask. - fn content_mask(&self) -> ContentMask { - self.window() - .next_frame - .content_mask_stack - .last() - .cloned() - .unwrap_or_else(|| ContentMask { - bounds: Bounds { - origin: Point::default(), - size: self.window().viewport_size, - }, - }) - } - - /// The size of an em for the base font of the application. Adjusting this value allows the - /// UI to scale, just like zooming a web page. - fn rem_size(&self) -> Pixels { - self.window().rem_size - } } impl Borrow for WindowContext<'_> { @@ -3052,34 +2136,6 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } - /// Add a listener for any mouse event that occurs in the window. - /// This is a fairly low level method. - /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. - pub fn on_mouse_event( - &mut self, - handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, - ) { - let handle = self.view().clone(); - self.window_cx.on_mouse_event(move |event, phase, cx| { - handle.update(cx, |view, cx| { - handler(view, event, phase, cx); - }) - }); - } - - /// Register a callback to be invoked when the given Key Event is dispatched to the window. - pub fn on_key_event( - &mut self, - handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, - ) { - let handle = self.view().clone(); - self.window_cx.on_key_event(move |event, phase, cx| { - handle.update(cx, |view, cx| { - handler(view, event, phase, cx); - }) - }); - } - /// Register a callback to be invoked when the given Action type is dispatched to the window. pub fn on_action( &mut self, diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs new file mode 100644 index 0000000000..9274082c5d --- /dev/null +++ b/crates/gpui/src/window/element_cx.rs @@ -0,0 +1,1129 @@ +use std::{ + any::{Any, TypeId}, + borrow::{Borrow, BorrowMut, Cow}, + mem, + rc::Rc, + sync::Arc, +}; + +use anyhow::Result; +use collections::{FxHashMap, FxHashSet}; +use derive_more::{Deref, DerefMut}; +use media::core_video::CVImageBuffer; +use smallvec::SmallVec; +use util::post_inc; + +use crate::{ + prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, + Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox, + EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, + InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, + Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, + RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingOrder, Style, + Surface, TextStyleRefinement, Underline, UnderlineStyle, Window, WindowContext, + SUBPIXEL_VARIANTS, +}; + +type AnyMouseListener = Box; + +pub(crate) struct RequestedInputHandler { + pub(crate) view_id: EntityId, + pub(crate) handler: Option, +} + +pub(crate) struct TooltipRequest { + pub(crate) view_id: EntityId, + pub(crate) tooltip: AnyTooltip, +} + +pub(crate) struct Frame { + pub(crate) focus: Option, + pub(crate) window_active: bool, + pub(crate) element_states: FxHashMap, + pub(crate) mouse_listeners: FxHashMap>, + pub(crate) dispatch_tree: DispatchTree, + pub(crate) scene: Scene, + pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, + pub(crate) z_index_stack: StackingOrder, + pub(crate) next_stacking_order_id: u32, + pub(crate) next_root_z_index: u8, + pub(crate) content_mask_stack: Vec>, + pub(crate) element_offset_stack: Vec>, + pub(crate) requested_input_handler: Option, + pub(crate) tooltip_request: Option, + pub(crate) cursor_styles: FxHashMap, + pub(crate) requested_cursor_style: Option, + pub(crate) view_stack: Vec, + pub(crate) reused_views: FxHashSet, + + #[cfg(any(test, feature = "test-support"))] + pub(crate) debug_bounds: collections::FxHashMap>, +} + +impl Frame { + pub(crate) fn new(dispatch_tree: DispatchTree) -> Self { + Frame { + focus: None, + window_active: false, + element_states: FxHashMap::default(), + mouse_listeners: FxHashMap::default(), + dispatch_tree, + scene: Scene::default(), + depth_map: Vec::new(), + z_index_stack: StackingOrder::default(), + next_stacking_order_id: 0, + next_root_z_index: 0, + content_mask_stack: Vec::new(), + element_offset_stack: Vec::new(), + requested_input_handler: None, + tooltip_request: None, + cursor_styles: FxHashMap::default(), + requested_cursor_style: None, + view_stack: Vec::new(), + reused_views: FxHashSet::default(), + + #[cfg(any(test, feature = "test-support"))] + debug_bounds: FxHashMap::default(), + } + } + + pub(crate) fn clear(&mut self) { + self.element_states.clear(); + self.mouse_listeners.values_mut().for_each(Vec::clear); + self.dispatch_tree.clear(); + self.depth_map.clear(); + self.next_stacking_order_id = 0; + self.next_root_z_index = 0; + self.reused_views.clear(); + self.scene.clear(); + self.requested_input_handler.take(); + self.tooltip_request.take(); + self.cursor_styles.clear(); + self.requested_cursor_style.take(); + debug_assert_eq!(self.view_stack.len(), 0); + } + + pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> { + self.focus + .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) + .unwrap_or_default() + } + + pub(crate) fn finish(&mut self, prev_frame: &mut Self) { + // Reuse mouse listeners that didn't change since the last frame. + for (type_id, listeners) in &mut prev_frame.mouse_listeners { + let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); + for (order, view_id, listener) in listeners.drain(..) { + if self.reused_views.contains(&view_id) { + next_listeners.push((order, view_id, listener)); + } + } + } + + // Reuse entries in the depth map that didn't change since the last frame. + for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { + if self.reused_views.contains(&view_id) { + match self + .depth_map + .binary_search_by(|(level, _, _)| order.cmp(level)) + { + Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)), + } + } + } + + // Retain element states for views that didn't change since the last frame. + for (element_id, state) in prev_frame.element_states.drain() { + if self.reused_views.contains(&state.parent_view_id) { + self.element_states.entry(element_id).or_insert(state); + } + } + + // Reuse geometry that didn't change since the last frame. + self.scene + .reuse_views(&self.reused_views, &mut prev_frame.scene); + self.scene.finish(); + } +} + +/// This context is used for assisting in the implementation of the element trait +#[derive(Deref, DerefMut)] +pub struct ElementContext<'a> { + pub(crate) cx: WindowContext<'a>, +} + +impl<'a> WindowContext<'a> { + pub fn with_element_context(&mut self, f: impl FnOnce(&mut ElementContext) -> R) -> R { + f(&mut ElementContext { + cx: WindowContext::new(self.app, self.window), + }) + } +} + +impl<'a> Borrow for ElementContext<'a> { + fn borrow(&self) -> &AppContext { + self.cx.app + } +} + +impl<'a> BorrowMut for ElementContext<'a> { + fn borrow_mut(&mut self) -> &mut AppContext { + self.cx.borrow_mut() + } +} + +impl<'a> Borrow> for ElementContext<'a> { + fn borrow(&self) -> &WindowContext<'a> { + &self.cx + } +} + +impl<'a> BorrowMut> for ElementContext<'a> { + fn borrow_mut(&mut self) -> &mut WindowContext<'a> { + &mut self.cx + } +} + +impl<'a> Borrow for ElementContext<'a> { + fn borrow(&self) -> &Window { + self.cx.window + } +} + +impl<'a> BorrowMut for ElementContext<'a> { + fn borrow_mut(&mut self) -> &mut Window { + self.cx.borrow_mut() + } +} + +impl<'a> Context for ElementContext<'a> { + type Result = as Context>::Result; + + fn new_model( + &mut self, + build_model: impl FnOnce(&mut crate::ModelContext<'_, T>) -> T, + ) -> Self::Result> { + self.cx.new_model(build_model) + } + + fn update_model( + &mut self, + handle: &crate::Model, + update: impl FnOnce(&mut T, &mut crate::ModelContext<'_, T>) -> R, + ) -> Self::Result + where + T: 'static, + { + self.cx.update_model(handle, update) + } + + fn read_model( + &self, + handle: &crate::Model, + read: impl FnOnce(&T, &AppContext) -> R, + ) -> Self::Result + where + T: 'static, + { + self.cx.read_model(handle, read) + } + + fn update_window(&mut self, window: crate::AnyWindowHandle, f: F) -> Result + where + F: FnOnce(crate::AnyView, &mut WindowContext<'_>) -> T, + { + self.cx.update_window(window, f) + } + + fn read_window( + &self, + window: &crate::WindowHandle, + read: impl FnOnce(crate::View, &AppContext) -> R, + ) -> Result + where + T: 'static, + { + self.cx.read_window(window, read) + } +} + +impl<'a> VisualContext for ElementContext<'a> { + fn new_view( + &mut self, + build_view: impl FnOnce(&mut crate::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Render, + { + self.cx.new_view(build_view) + } + + fn update_view( + &mut self, + view: &crate::View, + update: impl FnOnce(&mut V, &mut crate::ViewContext<'_, V>) -> R, + ) -> Self::Result { + self.cx.update_view(view, update) + } + + fn replace_root_view( + &mut self, + build_view: impl FnOnce(&mut crate::ViewContext<'_, V>) -> V, + ) -> Self::Result> + where + V: 'static + Render, + { + self.cx.replace_root_view(build_view) + } + + fn focus_view(&mut self, view: &crate::View) -> Self::Result<()> + where + V: crate::FocusableView, + { + self.cx.focus_view(view) + } + + fn dismiss_view(&mut self, view: &crate::View) -> Self::Result<()> + where + V: crate::ManagedView, + { + self.cx.dismiss_view(view) + } +} + +impl<'a> ElementContext<'a> { + pub(crate) fn reuse_view(&mut self) { + let view_id = self.parent_view_id(); + let grafted_view_ids = self + .cx + .window + .next_frame + .dispatch_tree + .reuse_view(view_id, &mut self.cx.window.rendered_frame.dispatch_tree); + for view_id in grafted_view_ids { + assert!(self.window.next_frame.reused_views.insert(view_id)); + + // Reuse the previous input handler requested during painting of the reused view. + if self + .window + .rendered_frame + .requested_input_handler + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.requested_input_handler = + self.window.rendered_frame.requested_input_handler.take(); + } + + // Reuse the tooltip previously requested during painting of the reused view. + if self + .window + .rendered_frame + .tooltip_request + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.tooltip_request = + self.window.rendered_frame.tooltip_request.take(); + } + + // Reuse the cursor styles previously requested during painting of the reused view. + if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); + } + } + } + + pub fn with_text_style(&mut self, style: Option, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + if let Some(style) = style { + self.push_text_style(style); + let result = f(self); + self.pop_text_style(); + result + } else { + f(self) + } + } + + /// Updates the cursor style at the platform level. + pub fn set_cursor_style(&mut self, style: CursorStyle) { + let view_id = self.parent_view_id(); + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); + } + + /// Sets a tooltip to be rendered for the upcoming frame + pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { + let view_id = self.parent_view_id(); + self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); + } + + /// Pushes the given element id onto the global stack and invokes the given closure + /// with a `GlobalElementId`, which disambiguates the given id in the context of its ancestor + /// ids. Because elements are discarded and recreated on each frame, the `GlobalElementId` is + /// used to associate state with identified elements across separate frames. + pub fn with_element_id( + &mut self, + id: Option>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + if let Some(id) = id.map(Into::into) { + let window = self.window_mut(); + window.element_id_stack.push(id); + let result = f(self); + let window: &mut Window = self.borrow_mut(); + window.element_id_stack.pop(); + result + } else { + f(self) + } + } + + /// Invoke the given function with the given content mask after intersecting it + /// with the current mask. + pub fn with_content_mask( + &mut self, + mask: Option>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + if let Some(mask) = mask { + let mask = mask.intersect(&self.content_mask()); + self.window_mut().next_frame.content_mask_stack.push(mask); + let result = f(self); + self.window_mut().next_frame.content_mask_stack.pop(); + result + } else { + f(self) + } + } + + /// Invoke the given function with the content mask reset to that + /// of the window. + pub fn break_content_mask(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + let mask = ContentMask { + bounds: Bounds { + origin: Point::default(), + size: self.window().viewport_size, + }, + }; + let new_stacking_order_id = + post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); + let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); + let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); + self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; + self.window_mut() + .next_frame + .z_index_stack + .push(new_root_z_index); + self.window_mut().next_frame.content_mask_stack.push(mask); + let result = f(self); + self.window_mut().next_frame.content_mask_stack.pop(); + self.window_mut().next_frame.z_index_stack = old_stacking_order; + result + } + + /// Called during painting to invoke the given closure in a new stacking context. The given + /// z-index is interpreted relative to the previous call to `stack`. + pub fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { + let new_stacking_order_id = + post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); + let old_stacking_order_id = mem::replace( + &mut self.window_mut().next_frame.z_index_stack.id, + new_stacking_order_id, + ); + self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; + self.window_mut().next_frame.z_index_stack.push(z_index); + let result = f(self); + self.window_mut().next_frame.z_index_stack.id = old_stacking_order_id; + self.window_mut().next_frame.z_index_stack.pop(); + result + } + + /// Updates the global element offset relative to the current offset. This is used to implement + /// scrolling. + pub fn with_element_offset( + &mut self, + offset: Point, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + if offset.is_zero() { + return f(self); + }; + + let abs_offset = self.element_offset() + offset; + self.with_absolute_element_offset(abs_offset, f) + } + + /// Updates the global element offset based on the given offset. This is used to implement + /// drag handles and other manual painting of elements. + pub fn with_absolute_element_offset( + &mut self, + offset: Point, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + self.window_mut() + .next_frame + .element_offset_stack + .push(offset); + let result = f(self); + self.window_mut().next_frame.element_offset_stack.pop(); + result + } + + /// Obtain the current element offset. + pub fn element_offset(&self) -> Point { + self.window() + .next_frame + .element_offset_stack + .last() + .copied() + .unwrap_or_default() + } + + /// Obtain the current content mask. + pub fn content_mask(&self) -> ContentMask { + self.window() + .next_frame + .content_mask_stack + .last() + .cloned() + .unwrap_or_else(|| ContentMask { + bounds: Bounds { + origin: Point::default(), + size: self.window().viewport_size, + }, + }) + } + + /// The size of an em for the base font of the application. Adjusting this value allows the + /// UI to scale, just like zooming a web page. + pub fn rem_size(&self) -> Pixels { + self.window().rem_size + } + + /// Updates or initializes state for an element with the given id that lives across multiple + /// frames. If an element with this ID existed in the rendered frame, its state will be passed + /// to the given closure. The state returned by the closure will be stored so it can be referenced + /// when drawing the next frame. + pub fn with_element_state( + &mut self, + id: ElementId, + f: impl FnOnce(Option, &mut Self) -> (R, S), + ) -> R + where + S: 'static, + { + self.with_element_id(Some(id), |cx| { + let global_id = cx.window().element_id_stack.clone(); + + if let Some(any) = cx + .window_mut() + .next_frame + .element_states + .remove(&global_id) + .or_else(|| { + cx.window_mut() + .rendered_frame + .element_states + .remove(&global_id) + }) + { + let ElementStateBox { + inner, + parent_view_id, + #[cfg(debug_assertions)] + type_name + } = any; + // Using the extra inner option to avoid needing to reallocate a new box. + let mut state_box = inner + .downcast::>() + .map_err(|_| { + #[cfg(debug_assertions)] + { + anyhow::anyhow!( + "invalid element state type for id, requested_type {:?}, actual type: {:?}", + std::any::type_name::(), + type_name + ) + } + + #[cfg(not(debug_assertions))] + { + anyhow::anyhow!( + "invalid element state type for id, requested_type {:?}", + std::any::type_name::(), + ) + } + }) + .unwrap(); + + // Actual: Option <- View + // Requested: () <- AnyElement + let state = state_box + .take() + .expect("element state is already on the stack"); + let (result, state) = f(Some(state), cx); + state_box.replace(state); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, ElementStateBox { + inner: state_box, + parent_view_id, + #[cfg(debug_assertions)] + type_name + }); + result + } else { + let (result, state) = f(None, cx); + let parent_view_id = cx.parent_view_id(); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, + ElementStateBox { + inner: Box::new(Some(state)), + parent_view_id, + #[cfg(debug_assertions)] + type_name: std::any::type_name::() + } + + ); + result + } + }) + } + /// Paint one or more drop shadows into the scene for the next frame at the current z-index. + pub fn paint_shadows( + &mut self, + bounds: Bounds, + corner_radii: Corners, + shadows: &[BoxShadow], + ) { + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + let view_id = self.parent_view_id(); + let window = &mut *self.window; + for shadow in shadows { + let mut shadow_bounds = bounds; + shadow_bounds.origin += shadow.offset; + shadow_bounds.dilate(shadow.spread_radius); + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + Shadow { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds: shadow_bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + corner_radii: corner_radii.scale(scale_factor), + color: shadow.color, + blur_radius: shadow.blur_radius.scale(scale_factor), + }, + ); + } + } + + /// Paint one or more quads into the scene for the next frame at the current stacking context. + /// Quads are colored rectangular regions with an optional background, border, and corner radius. + /// see [`fill`], [`outline`], and [`quad`] to construct this type. + pub fn paint_quad(&mut self, quad: PaintQuad) { + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + let view_id = self.parent_view_id(); + + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + Quad { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds: quad.bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + background: quad.background, + border_color: quad.border_color, + corner_radii: quad.corner_radii.scale(scale_factor), + border_widths: quad.border_widths.scale(scale_factor), + }, + ); + } + + /// Paint the given `Path` into the scene for the next frame at the current z-index. + pub fn paint_path(&mut self, mut path: Path, color: impl Into) { + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + let view_id = self.parent_view_id(); + + path.content_mask = content_mask; + path.color = color.into(); + path.view_id = view_id.into(); + let window = &mut *self.window; + window + .next_frame + .scene + .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); + } + + /// Paint an underline into the scene for the next frame at the current z-index. + pub fn paint_underline( + &mut self, + origin: Point, + width: Pixels, + style: &UnderlineStyle, + ) { + let scale_factor = self.scale_factor(); + let height = if style.wavy { + style.thickness * 3. + } else { + style.thickness + }; + let bounds = Bounds { + origin, + size: size(width, height), + }; + let content_mask = self.content_mask(); + let view_id = self.parent_view_id(); + + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + Underline { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds: bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + thickness: style.thickness.scale(scale_factor), + color: style.color.unwrap_or_default(), + wavy: style.wavy, + }, + ); + } + + /// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index. + /// The y component of the origin is the baseline of the glyph. + pub fn paint_glyph( + &mut self, + origin: Point, + font_id: FontId, + glyph_id: GlyphId, + font_size: Pixels, + color: Hsla, + ) -> Result<()> { + let scale_factor = self.scale_factor(); + let glyph_origin = origin.scale(scale_factor); + let subpixel_variant = Point { + x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, + y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8, + }; + let params = RenderGlyphParams { + font_id, + glyph_id, + font_size, + subpixel_variant, + scale_factor, + is_emoji: false, + }; + + let raster_bounds = self.text_system().raster_bounds(¶ms)?; + if !raster_bounds.is_zero() { + let tile = + self.window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?; + Ok((size, Cow::Owned(bytes))) + })?; + let bounds = Bounds { + origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into), + size: tile.bounds.size.map(Into::into), + }; + let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.parent_view_id(); + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + MonochromeSprite { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds, + content_mask, + color, + tile, + }, + ); + } + Ok(()) + } + + /// Paint an emoji glyph into the scene for the next frame at the current z-index. + /// The y component of the origin is the baseline of the glyph. + pub fn paint_emoji( + &mut self, + origin: Point, + font_id: FontId, + glyph_id: GlyphId, + font_size: Pixels, + ) -> Result<()> { + let scale_factor = self.scale_factor(); + let glyph_origin = origin.scale(scale_factor); + let params = RenderGlyphParams { + font_id, + glyph_id, + font_size, + // We don't render emojis with subpixel variants. + subpixel_variant: Default::default(), + scale_factor, + is_emoji: true, + }; + + let raster_bounds = self.text_system().raster_bounds(¶ms)?; + if !raster_bounds.is_zero() { + let tile = + self.window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?; + Ok((size, Cow::Owned(bytes))) + })?; + let bounds = Bounds { + origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into), + size: tile.bounds.size.map(Into::into), + }; + let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.parent_view_id(); + let window = &mut *self.window; + + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + PolychromeSprite { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds, + corner_radii: Default::default(), + content_mask, + tile, + grayscale: false, + }, + ); + } + Ok(()) + } + + /// Paint a monochrome SVG into the scene for the next frame at the current stacking context. + pub fn paint_svg( + &mut self, + bounds: Bounds, + path: SharedString, + color: Hsla, + ) -> Result<()> { + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + // Render the SVG at twice the size to get a higher quality result. + let params = RenderSvgParams { + path, + size: bounds + .size + .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)), + }; + + let tile = + self.window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + let bytes = self.svg_renderer.render(¶ms)?; + Ok((params.size, Cow::Owned(bytes))) + })?; + let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.parent_view_id(); + + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + MonochromeSprite { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds, + content_mask, + color, + tile, + }, + ); + + Ok(()) + } + + /// Paint an image into the scene for the next frame at the current z-index. + pub fn paint_image( + &mut self, + bounds: Bounds, + corner_radii: Corners, + data: Arc, + grayscale: bool, + ) -> Result<()> { + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + let params = RenderImageParams { image_id: data.id }; + + let tile = self + .window + .sprite_atlas + .get_or_insert_with(¶ms.clone().into(), &mut || { + Ok((data.size(), Cow::Borrowed(data.as_bytes()))) + })?; + let content_mask = self.content_mask().scale(scale_factor); + let corner_radii = corner_radii.scale(scale_factor); + let view_id = self.parent_view_id(); + + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + PolychromeSprite { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds, + content_mask, + corner_radii, + tile, + grayscale, + }, + ); + Ok(()) + } + + /// Paint a surface into the scene for the next frame at the current z-index. + pub fn paint_surface(&mut self, bounds: Bounds, image_buffer: CVImageBuffer) { + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.parent_view_id(); + let window = &mut *self.window; + window.next_frame.scene.insert( + &window.next_frame.z_index_stack, + Surface { + view_id: view_id.into(), + layer_id: 0, + order: 0, + bounds, + content_mask, + image_buffer, + }, + ); + } + + #[must_use] + /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which + /// layout is being requested, along with the layout ids of any children. This method is called during + /// calls to the `Element::layout` trait method and enables any element to participate in layout. + pub fn request_layout( + &mut self, + style: &Style, + children: impl IntoIterator, + ) -> LayoutId { + self.app.layout_id_buffer.clear(); + self.app.layout_id_buffer.extend(children); + let rem_size = self.rem_size(); + + self.cx + .window + .layout_engine + .as_mut() + .unwrap() + .request_layout(style, rem_size, &self.cx.app.layout_id_buffer) + } + + /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children, + /// this variant takes a function that is invoked during layout so you can use arbitrary logic to + /// determine the element's size. One place this is used internally is when measuring text. + /// + /// The given closure is invoked at layout time with the known dimensions and available space and + /// returns a `Size`. + pub fn request_measured_layout< + F: FnMut(Size>, Size, &mut WindowContext) -> Size + + 'static, + >( + &mut self, + style: Style, + measure: F, + ) -> LayoutId { + let rem_size = self.rem_size(); + self.window + .layout_engine + .as_mut() + .unwrap() + .request_measured_layout(style, rem_size, measure) + } + + /// Compute the layout for the given id within the given available space. + /// This method is called for its side effect, typically by the framework prior to painting. + /// After calling it, you can request the bounds of the given layout node id or any descendant. + pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { + let mut layout_engine = self.window.layout_engine.take().unwrap(); + layout_engine.compute_layout(layout_id, available_space, self); + self.window.layout_engine = Some(layout_engine); + } + + /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not + /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically + /// in order to pass your element its `Bounds` automatically. + pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Bounds { + let mut bounds = self + .window + .layout_engine + .as_mut() + .unwrap() + .layout_bounds(layout_id) + .map(Into::into); + bounds.origin += self.element_offset(); + bounds + } + + pub(crate) fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> { + self.window + .layout_engine + .as_ref() + .unwrap() + .requested_style(layout_id) + } + + /// Called during painting to track which z-index is on top at each pixel position + pub fn add_opaque_layer(&mut self, bounds: Bounds) { + let stacking_order = self.window.next_frame.z_index_stack.clone(); + let view_id = self.parent_view_id(); + let depth_map = &mut self.window.next_frame.depth_map; + match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) { + Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)), + } + } + + /// Invoke the given function with the given focus handle present on the key dispatch stack. + /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. + pub fn with_key_dispatch( + &mut self, + context: Option, + focus_handle: Option, + f: impl FnOnce(Option, &mut Self) -> R, + ) -> R { + let window = &mut self.window; + let focus_id = focus_handle.as_ref().map(|handle| handle.id); + window + .next_frame + .dispatch_tree + .push_node(context.clone(), focus_id, None); + + let result = f(focus_handle, self); + + self.window.next_frame.dispatch_tree.pop_node(); + + result + } + + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to layout views. + pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } + }) + } + + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to paint views. + pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + self.window + .next_frame + .dispatch_tree + .push_node(None, None, Some(view_id)); + let result = f(self); + self.window.next_frame.dispatch_tree.pop_node(); + self.window.next_frame.view_stack.pop(); + result + } + }) + } + + /// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the + /// platform to receive textual input with proper integration with concerns such + /// as IME interactions. This handler will be active for the upcoming frame until the following frame is + /// rendered. + /// + /// [element_input_handler]: crate::ElementInputHandler + pub fn handle_input(&mut self, focus_handle: &FocusHandle, input_handler: impl InputHandler) { + if focus_handle.is_focused(self) { + let view_id = self.parent_view_id(); + self.window.next_frame.requested_input_handler = Some(RequestedInputHandler { + view_id, + handler: Some(PlatformInputHandler::new( + self.to_async(), + Box::new(input_handler), + )), + }) + } + } + + /// Register a mouse event listener on the window for the next frame. The type of event + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + pub fn on_mouse_event( + &mut self, + mut handler: impl FnMut(&Event, DispatchPhase, &mut ElementContext) + 'static, + ) { + let view_id = self.parent_view_id(); + let order = self.window.next_frame.z_index_stack.clone(); + self.window + .next_frame + .mouse_listeners + .entry(TypeId::of::()) + .or_default() + .push(( + order, + view_id, + Box::new( + move |event: &dyn Any, phase: DispatchPhase, cx: &mut ElementContext<'_>| { + handler(event.downcast_ref().unwrap(), phase, cx) + }, + ), + )) + } + + /// Register a key event listener on the window for the next frame. The type of event + /// is determined by the first parameter of the given listener. When the next frame is rendered + /// the listener will be cleared. + /// + /// This is a fairly low-level method, so prefer using event handlers on elements unless you have + /// a specific need to register a global listener. + pub fn on_key_event( + &mut self, + listener: impl Fn(&Event, DispatchPhase, &mut ElementContext) + 'static, + ) { + self.window.next_frame.dispatch_tree.on_key_event(Rc::new( + move |event: &dyn Any, phase, cx: &mut ElementContext<'_>| { + if let Some(event) = event.downcast_ref::() { + listener(event, phase, cx) + } + }, + )); + } +} diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 9ab62947fd..c67fbfc4d0 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1,11 +1,11 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ - div, fill, point, px, relative, AnyElement, AvailableSpace, BorrowWindow, Bounds, - DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, - Hsla, InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, - Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, - MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, - TextStyle, TextSystem, UnderlineStyle, WeakView, WhiteSpace, WindowContext, + div, fill, point, px, relative, AnyElement, AvailableSpace, Bounds, DispatchPhase, Element, + ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla, + InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity, + IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, + Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, + UnderlineStyle, WeakView, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -81,7 +81,7 @@ impl LayoutCell { origin: Point, layout: &LayoutState, _visible_bounds: Bounds, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let pos = { let point = self.point; @@ -120,7 +120,7 @@ impl LayoutRect { } } - fn paint(&self, origin: Point, layout: &LayoutState, cx: &mut WindowContext) { + fn paint(&self, origin: Point, layout: &LayoutState, cx: &mut ElementContext) { let position = { let alac_point = self.point; point( @@ -365,7 +365,7 @@ impl TerminalElement { result } - fn compute_layout(&self, bounds: Bounds, cx: &mut WindowContext) -> LayoutState { + fn compute_layout(&self, bounds: Bounds, cx: &mut ElementContext) -> LayoutState { let settings = ThemeSettings::get_global(cx).clone(); let buffer_font_size = settings.buffer_font_size(cx); @@ -590,7 +590,7 @@ impl TerminalElement { origin: Point, mode: TermMode, bounds: Bounds, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); @@ -722,7 +722,7 @@ impl Element for TerminalElement { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext<'_>, + cx: &mut ElementContext<'_>, ) -> (LayoutId, Self::State) { let (layout_id, interactive_state) = self.interactivity @@ -741,7 +741,7 @@ impl Element for TerminalElement { &mut self, bounds: Bounds, state: &mut Self::State, - cx: &mut WindowContext<'_>, + cx: &mut ElementContext<'_>, ) { let mut layout = self.compute_layout(bounds, cx); diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 10c655909b..0bbbee2900 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -2,8 +2,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds, - DismissEvent, DispatchPhase, Element, ElementId, InteractiveBounds, IntoElement, LayoutId, - ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, + DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds, + IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, + VisualContext, WindowContext, }; use crate::{Clickable, Selectable}; @@ -134,7 +135,7 @@ impl Element for PopoverMenu { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (gpui::LayoutId, Self::State) { let mut menu_layout_id = None; @@ -188,7 +189,7 @@ impl Element for PopoverMenu { &mut self, _: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { if let Some(mut child) = element_state.child_element.take() { child.paint(cx); diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index ee1bd88015..b08f3911cb 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ - overlay, AnchorCorner, AnyElement, BorrowWindow, Bounds, DismissEvent, DispatchPhase, Element, - ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, - ParentElement, Pixels, Point, View, VisualContext, WindowContext, + overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, + ElementContext, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton, + MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, }; pub struct RightClickMenu { @@ -64,7 +64,7 @@ impl Element for RightClickMenu { fn request_layout( &mut self, element_state: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (gpui::LayoutId, Self::State) { let (menu, position) = if let Some(element_state) = element_state { (element_state.menu, element_state.position) @@ -116,7 +116,7 @@ impl Element for RightClickMenu { &mut self, bounds: Bounds, element_state: &mut Self::State, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { if let Some(mut child) = element_state.child_element.take() { child.paint(cx); diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 837d93db2d..91e6ed2450 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -2,9 +2,9 @@ pub use gpui::prelude::*; pub use gpui::{ - div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, - InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext, - WindowContext, + div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementContext, + ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, + ViewContext, WindowContext, }; pub use crate::clickable::*; diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index da990c530c..1819be7878 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -7,7 +7,7 @@ use std::time::Duration; use command_palette::CommandPalette; use editor::DisplayPoint; -use gpui::{Action, KeyBinding}; +use gpui::KeyBinding; pub use neovim_backed_binding_test_context::*; pub use neovim_backed_test_context::*; pub use vim_test_context::*; @@ -15,7 +15,7 @@ pub use vim_test_context::*; use indoc::indoc; use search::BufferSearchBar; -use crate::{insert::NormalBefore, motion, normal::InsertLineBelow, state::Mode, ModeIndicator}; +use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator}; #[gpui::test] async fn test_initially_disabled(cx: &mut gpui::TestAppContext) { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 64e09e67cb..6963ed3cae 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -710,7 +710,7 @@ mod element { pane_bounds: Bounds, axis_bounds: Bounds, workspace: WeakView, - cx: &mut WindowContext, + cx: &mut ElementContext, ) { let handle_bounds = Bounds { origin: pane_bounds.origin.apply_along(axis, |origin| { @@ -803,7 +803,7 @@ mod element { fn request_layout( &mut self, state: Option, - cx: &mut ui::prelude::WindowContext, + cx: &mut ui::prelude::ElementContext, ) -> (gpui::LayoutId, Self::State) { let mut style = Style::default(); style.flex_grow = 1.; @@ -820,7 +820,7 @@ mod element { &mut self, bounds: gpui::Bounds, state: &mut Self::State, - cx: &mut ui::prelude::WindowContext, + cx: &mut ui::prelude::ElementContext, ) { let flexes = self.flexes.lock().clone(); let len = self.children.len(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a95a143915..d10cef0c0f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -26,12 +26,12 @@ use futures::{ }; use gpui::{ actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView, - AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, Bounds, Context, - Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, - GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model, - ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, Render, Size, - Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, - WindowContext, WindowHandle, WindowOptions, + AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, + DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle, + FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, + ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, + Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -3539,9 +3539,14 @@ impl Render for Workspace { .border_b() .border_color(colors.border) .child( - canvas(cx.listener(|workspace, bounds, _| { - workspace.bounds = *bounds; - })) + canvas({ + let this = cx.view().clone(); + move |bounds, cx| { + this.update(cx, |this, _cx| { + this.bounds = *bounds; + }) + } + }) .absolute() .size_full(), ) @@ -4293,7 +4298,7 @@ impl Element for DisconnectedOverlay { fn request_layout( &mut self, _: Option, - cx: &mut WindowContext, + cx: &mut ElementContext, ) -> (LayoutId, Self::State) { let mut background = cx.theme().colors().elevated_surface_background; background.fade_out(0.2); @@ -4315,7 +4320,12 @@ impl Element for DisconnectedOverlay { (overlay.request_layout(cx), overlay) } - fn paint(&mut self, bounds: Bounds, overlay: &mut Self::State, cx: &mut WindowContext) { + fn paint( + &mut self, + bounds: Bounds, + overlay: &mut Self::State, + cx: &mut ElementContext, + ) { cx.with_z_index(u8::MAX, |cx| { cx.add_opaque_layer(bounds); overlay.paint(cx);