diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index fc7f8de8a0..685c37ac60 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -912,7 +912,7 @@ mod tests { display_map::{BlockContext, TransformBlock}, DisplayPoint, GutterDimensions, }; - use gpui::{px, Stateful, TestAppContext, VisualTestContext, WindowContext}; + use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext}; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; use project::FakeFs; use serde_json::json; @@ -1049,67 +1049,66 @@ mod tests { cx, ) }); + let editor = view.update(cx, |view, _| view.editor.clone()); view.next_notification(cx).await; - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (15, "collapsed context".into()), - (16, "diagnostic header".into()), - (25, "collapsed context".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - // - // main.rs - // - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - " let x = vec![];\n", - " let y = vec![];\n", - "\n", // supporting diagnostic - " a(x);\n", - " b(y);\n", - "\n", // supporting diagnostic - " // comment 1\n", - " // comment 2\n", - " c(y);\n", - "\n", // supporting diagnostic - " d(x);\n", - "\n", // context ellipsis - // diagnostic group 2 - "\n", // primary message - "\n", // padding - "fn main() {\n", - " let x = vec![];\n", - "\n", // supporting diagnostic - " let y = vec![];\n", - " a(x);\n", - "\n", // supporting diagnostic - " b(y);\n", - "\n", // context ellipsis - " c(y);\n", - " d(x);\n", - "\n", // supporting diagnostic - "}" - ) - ); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (15, "collapsed context".into()), + (16, "diagnostic header".into()), + (25, "collapsed context".into()), + ] + ); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + // + // main.rs + // + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + " let x = vec![];\n", + " let y = vec![];\n", + "\n", // supporting diagnostic + " a(x);\n", + " b(y);\n", + "\n", // supporting diagnostic + " // comment 1\n", + " // comment 2\n", + " c(y);\n", + "\n", // supporting diagnostic + " d(x);\n", + "\n", // context ellipsis + // diagnostic group 2 + "\n", // primary message + "\n", // padding + "fn main() {\n", + " let x = vec![];\n", + "\n", // supporting diagnostic + " let y = vec![];\n", + " a(x);\n", + "\n", // supporting diagnostic + " b(y);\n", + "\n", // context ellipsis + " c(y);\n", + " d(x);\n", + "\n", // supporting diagnostic + "}" + ) + ); - // Cursor is at the first diagnostic - view.editor.update(cx, |editor, cx| { - assert_eq!( - editor.selections.display_ranges(cx), - [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)] - ); - }); + // Cursor is at the first diagnostic + editor.update(cx, |editor, cx| { + assert_eq!( + editor.selections.display_ranges(cx), + [DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)] + ); }); // Diagnostics are added for another earlier path. @@ -1138,78 +1137,77 @@ mod tests { }); view.next_notification(cx).await; - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (7, "path header block".into()), - (9, "diagnostic header".into()), - (22, "collapsed context".into()), - (23, "diagnostic header".into()), - (32, "collapsed context".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - // - // consts.rs - // - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "const a: i32 = 'a';\n", - "\n", // supporting diagnostic - "const b: i32 = c;\n", - // - // main.rs - // - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - " let x = vec![];\n", - " let y = vec![];\n", - "\n", // supporting diagnostic - " a(x);\n", - " b(y);\n", - "\n", // supporting diagnostic - " // comment 1\n", - " // comment 2\n", - " c(y);\n", - "\n", // supporting diagnostic - " d(x);\n", - "\n", // collapsed context - // diagnostic group 2 - "\n", // primary message - "\n", // filename - "fn main() {\n", - " let x = vec![];\n", - "\n", // supporting diagnostic - " let y = vec![];\n", - " a(x);\n", - "\n", // supporting diagnostic - " b(y);\n", - "\n", // context ellipsis - " c(y);\n", - " d(x);\n", - "\n", // supporting diagnostic - "}" - ) - ); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (7, "path header block".into()), + (9, "diagnostic header".into()), + (22, "collapsed context".into()), + (23, "diagnostic header".into()), + (32, "collapsed context".into()), + ] + ); - // Cursor keeps its position. - view.editor.update(cx, |editor, cx| { - assert_eq!( - editor.selections.display_ranges(cx), - [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)] - ); - }); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + // + // consts.rs + // + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "const a: i32 = 'a';\n", + "\n", // supporting diagnostic + "const b: i32 = c;\n", + // + // main.rs + // + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + " let x = vec![];\n", + " let y = vec![];\n", + "\n", // supporting diagnostic + " a(x);\n", + " b(y);\n", + "\n", // supporting diagnostic + " // comment 1\n", + " // comment 2\n", + " c(y);\n", + "\n", // supporting diagnostic + " d(x);\n", + "\n", // collapsed context + // diagnostic group 2 + "\n", // primary message + "\n", // filename + "fn main() {\n", + " let x = vec![];\n", + "\n", // supporting diagnostic + " let y = vec![];\n", + " a(x);\n", + "\n", // supporting diagnostic + " b(y);\n", + "\n", // context ellipsis + " c(y);\n", + " d(x);\n", + "\n", // supporting diagnostic + "}" + ) + ); + + // Cursor keeps its position. + editor.update(cx, |editor, cx| { + assert_eq!( + editor.selections.display_ranges(cx), + [DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)] + ); }); // Diagnostics are added to the first path @@ -1254,80 +1252,79 @@ mod tests { }); view.next_notification(cx).await; - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (7, "collapsed context".into()), - (8, "diagnostic header".into()), - (13, "path header block".into()), - (15, "diagnostic header".into()), - (28, "collapsed context".into()), - (29, "diagnostic header".into()), - (38, "collapsed context".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - // - // consts.rs - // - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "const a: i32 = 'a';\n", - "\n", // supporting diagnostic - "const b: i32 = c;\n", - "\n", // context ellipsis - // diagnostic group 2 - "\n", // primary message - "\n", // padding - "const a: i32 = 'a';\n", - "const b: i32 = c;\n", - "\n", // supporting diagnostic - // - // main.rs - // - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - " let x = vec![];\n", - " let y = vec![];\n", - "\n", // supporting diagnostic - " a(x);\n", - " b(y);\n", - "\n", // supporting diagnostic - " // comment 1\n", - " // comment 2\n", - " c(y);\n", - "\n", // supporting diagnostic - " d(x);\n", - "\n", // context ellipsis - // diagnostic group 2 - "\n", // primary message - "\n", // filename - "fn main() {\n", - " let x = vec![];\n", - "\n", // supporting diagnostic - " let y = vec![];\n", - " a(x);\n", - "\n", // supporting diagnostic - " b(y);\n", - "\n", // context ellipsis - " c(y);\n", - " d(x);\n", - "\n", // supporting diagnostic - "}" - ) - ); - }); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (7, "collapsed context".into()), + (8, "diagnostic header".into()), + (13, "path header block".into()), + (15, "diagnostic header".into()), + (28, "collapsed context".into()), + (29, "diagnostic header".into()), + (38, "collapsed context".into()), + ] + ); + + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + // + // consts.rs + // + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "const a: i32 = 'a';\n", + "\n", // supporting diagnostic + "const b: i32 = c;\n", + "\n", // context ellipsis + // diagnostic group 2 + "\n", // primary message + "\n", // padding + "const a: i32 = 'a';\n", + "const b: i32 = c;\n", + "\n", // supporting diagnostic + // + // main.rs + // + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + " let x = vec![];\n", + " let y = vec![];\n", + "\n", // supporting diagnostic + " a(x);\n", + " b(y);\n", + "\n", // supporting diagnostic + " // comment 1\n", + " // comment 2\n", + " c(y);\n", + "\n", // supporting diagnostic + " d(x);\n", + "\n", // context ellipsis + // diagnostic group 2 + "\n", // primary message + "\n", // filename + "fn main() {\n", + " let x = vec![];\n", + "\n", // supporting diagnostic + " let y = vec![];\n", + " a(x);\n", + "\n", // supporting diagnostic + " b(y);\n", + "\n", // context ellipsis + " c(y);\n", + " d(x);\n", + "\n", // supporting diagnostic + "}" + ) + ); } #[gpui::test] @@ -1364,6 +1361,7 @@ mod tests { cx, ) }); + let editor = view.update(cx, |view, _| view.editor.clone()); // Two language servers start updating diagnostics project.update(cx, |project, cx| { @@ -1397,27 +1395,25 @@ mod tests { // Only the first language server's diagnostics are shown. cx.executor().run_until_parked(); - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "a();\n", // - "b();", - ) - ); - }); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + ] + ); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "a();\n", // + "b();", + ) + ); // The second language server finishes project.update(cx, |project, cx| { @@ -1445,36 +1441,34 @@ mod tests { // Both language server's diagnostics are shown. cx.executor().run_until_parked(); - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (6, "collapsed context".into()), - (7, "diagnostic header".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "a();\n", // location - "b();\n", // - "\n", // collapsed context - // diagnostic group 2 - "\n", // primary message - "\n", // padding - "a();\n", // context - "b();\n", // - "c();", // context - ) - ); - }); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (6, "collapsed context".into()), + (7, "diagnostic header".into()), + ] + ); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "a();\n", // location + "b();\n", // + "\n", // collapsed context + // diagnostic group 2 + "\n", // primary message + "\n", // padding + "a();\n", // context + "b();\n", // + "c();", // context + ) + ); // Both language servers start updating diagnostics, and the first server finishes. project.update(cx, |project, cx| { @@ -1513,37 +1507,35 @@ mod tests { // Only the first language server's diagnostics are updated. cx.executor().run_until_parked(); - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (7, "collapsed context".into()), - (8, "diagnostic header".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "a();\n", // location - "b();\n", // - "c();\n", // context - "\n", // collapsed context - // diagnostic group 2 - "\n", // primary message - "\n", // padding - "b();\n", // context - "c();\n", // - "d();", // context - ) - ); - }); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (7, "collapsed context".into()), + (8, "diagnostic header".into()), + ] + ); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "a();\n", // location + "b();\n", // + "c();\n", // context + "\n", // collapsed context + // diagnostic group 2 + "\n", // primary message + "\n", // padding + "b();\n", // context + "c();\n", // + "d();", // context + ) + ); // The second language server finishes. project.update(cx, |project, cx| { @@ -1571,37 +1563,35 @@ mod tests { // Both language servers' diagnostics are updated. cx.executor().run_until_parked(); - view.update(cx, |view, cx| { - assert_eq!( - editor_blocks(&view.editor, cx), - [ - (0, "path header block".into()), - (2, "diagnostic header".into()), - (7, "collapsed context".into()), - (8, "diagnostic header".into()), - ] - ); - assert_eq!( - view.editor.update(cx, |editor, cx| editor.display_text(cx)), - concat!( - "\n", // filename - "\n", // padding - // diagnostic group 1 - "\n", // primary message - "\n", // padding - "b();\n", // location - "c();\n", // - "d();\n", // context - "\n", // collapsed context - // diagnostic group 2 - "\n", // primary message - "\n", // padding - "c();\n", // context - "d();\n", // - "e();", // context - ) - ); - }); + assert_eq!( + editor_blocks(&editor, cx), + [ + (0, "path header block".into()), + (2, "diagnostic header".into()), + (7, "collapsed context".into()), + (8, "diagnostic header".into()), + ] + ); + assert_eq!( + editor.update(cx, |editor, cx| editor.display_text(cx)), + concat!( + "\n", // filename + "\n", // padding + // diagnostic group 1 + "\n", // primary message + "\n", // padding + "b();\n", // location + "c();\n", // + "d();\n", // context + "\n", // collapsed context + // diagnostic group 2 + "\n", // primary message + "\n", // padding + "c();\n", // context + "d();\n", // + "e();", // context + ) + ); } fn init_test(cx: &mut TestAppContext) { @@ -1618,45 +1608,58 @@ mod tests { }); } - fn editor_blocks(editor: &View, cx: &mut WindowContext) -> Vec<(u32, SharedString)> { - 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: SharedString = match block { - TransformBlock::Custom(block) => cx.with_element_context({ - |cx| -> Option { - let mut element = block.render(&mut BlockContext { - context: cx, - anchor_x: px(0.), - gutter_dimensions: &GutterDimensions::default(), - line_height: px(0.), - em_width: px(0.), - max_width: px(0.), - block_id: ix, - editor_style: &editor::EditorStyle::default(), - }); - let element = element.downcast_mut::>().unwrap(); - element.interactivity().element_id.clone()?.try_into().ok() - } - })?, + fn editor_blocks( + editor: &View, + cx: &mut VisualTestContext, + ) -> Vec<(u32, SharedString)> { + let mut blocks = Vec::new(); + cx.draw(gpui::Point::default(), AvailableSpace::min_size(), |cx| { + editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + blocks.extend( + snapshot + .blocks_in_range(0..snapshot.max_point().row()) + .enumerate() + .filter_map(|(ix, (row, block))| { + let name: SharedString = match block { + TransformBlock::Custom(block) => { + let mut element = block.render(&mut BlockContext { + context: cx, + anchor_x: px(0.), + gutter_dimensions: &GutterDimensions::default(), + line_height: px(0.), + em_width: px(0.), + max_width: px(0.), + block_id: ix, + editor_style: &editor::EditorStyle::default(), + }); + let element = element.downcast_mut::>().unwrap(); + element + .interactivity() + .element_id + .clone()? + .try_into() + .ok()? + } - TransformBlock::ExcerptHeader { - starts_new_buffer, .. - } => { - if *starts_new_buffer { - "path header block".into() - } else { - "collapsed context".into() - } - } - }; + TransformBlock::ExcerptHeader { + starts_new_buffer, .. + } => { + if *starts_new_buffer { + "path header block".into() + } else { + "collapsed context".into() + } + } + }; - Some((row, name)) - }) - .collect() - }) + Some((row, name)) + }), + ) + }); + + div().into_any() + }); + blocks } } diff --git a/crates/editor/src/blame_entry_tooltip.rs b/crates/editor/src/blame_entry_tooltip.rs index ac8c6bfc05..3732cf4caf 100644 --- a/crates/editor/src/blame_entry_tooltip.rs +++ b/crates/editor/src/blame_entry_tooltip.rs @@ -39,17 +39,15 @@ impl<'a> CommitAvatar<'a> { let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha); - let element = cx.with_element_context(|cx| { - match cx.use_cached_asset::(&avatar_url) { - // Loading or no avatar found - None | Some(None) => Icon::new(IconName::Person) - .color(Color::Muted) - .into_element() - .into_any(), - // Found - Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(), - } - }); + let element = match cx.use_cached_asset::(&avatar_url) { + // Loading or no avatar found + None | Some(None) => Icon::new(IconName::Person) + .color(Color::Muted) + .into_element() + .into_any(), + // Found + Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(), + }; Some(element) } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6de21b0d08..026d5365cf 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::{EditorStyle, GutterDimensions}; use collections::{Bound, HashMap, HashSet}; -use gpui::{AnyElement, ElementContext, Pixels}; +use gpui::{AnyElement, Pixels, WindowContext}; use language::{BufferSnapshot, Chunk, Patch, Point}; use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _}; use parking_lot::Mutex; @@ -82,7 +82,7 @@ pub enum BlockStyle { } pub struct BlockContext<'a, 'b> { - pub context: &'b mut ElementContext<'a>, + pub context: &'b mut WindowContext<'a>, pub anchor_x: Pixels, pub max_width: Pixels, pub gutter_dimensions: &'b GutterDimensions, @@ -934,7 +934,7 @@ impl BlockDisposition { } impl<'a> Deref for BlockContext<'a, '_> { - type Target = ElementContext<'a>; + type Target = WindowContext<'a>; fn deref(&self) -> &Self::Target { self.context diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1000952cf6..42b6bfb90b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -23,12 +23,11 @@ use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid}; use gpui::{ anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem, - ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementContext, - ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, - ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, - StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, - ViewContext, WeakView, WindowContext, + ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hitbox, + Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, + ShapedLine, SharedString, Size, Stateful, StatefulInteractiveElement, Style, Styled, TextRun, + TextStyle, TextStyleRefinement, View, ViewContext, WeakView, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -367,7 +366,7 @@ impl EditorElement { register_action(view, cx, Editor::open_active_item_in_terminal) } - fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) { + fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) { let position_map = layout.position_map.clone(); cx.on_key_event({ let editor = self.editor.clone(); @@ -691,7 +690,7 @@ impl EditorElement { snapshot: &EditorSnapshot, start_row: u32, end_row: u32, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> ( Vec<(PlayerColor, Vec)>, BTreeMap, @@ -819,7 +818,7 @@ impl EditorElement { scroll_pixel_position: gpui::Point, line_height: Pixels, line_layouts: &[LineWithInvisibles], - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Vec { snapshot .folds_in_range(visible_anchor_range.clone()) @@ -887,7 +886,7 @@ impl EditorElement { line_height: Pixels, em_width: Pixels, autoscroll_containing_element: bool, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Vec { let mut autoscroll_bounds = None; let cursor_layouts = self.editor.update(cx, |editor, cx| { @@ -993,7 +992,7 @@ impl EditorElement { color: self.style.background, is_top_row: cursor_position.row() == 0, }); - cx.with_element_context(|cx| cursor.layout(content_origin, cursor_name, cx)); + cursor.layout(content_origin, cursor_name, cx); cursors.push(cursor); } } @@ -1013,7 +1012,7 @@ impl EditorElement { bounds: Bounds, scroll_position: gpui::Point, rows_per_page: f32, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; let show_scrollbars = match scrollbar_settings.show { @@ -1082,7 +1081,7 @@ impl EditorElement { gutter_settings: crate::editor_settings::Gutter, scroll_pixel_position: gpui::Point, gutter_hitbox: &Hitbox, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Vec> { let mut indicators = self.editor.update(cx, |editor, cx| { editor.render_fold_indicators( @@ -1155,7 +1154,7 @@ impl EditorElement { content_origin: gpui::Point, scroll_pixel_position: gpui::Point, line_height: Pixels, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { if !self .editor @@ -1220,7 +1219,7 @@ impl EditorElement { line_height: Pixels, gutter_hitbox: &Hitbox, max_width: Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option> { if !self .editor @@ -1285,7 +1284,7 @@ impl EditorElement { scroll_pixel_position: gpui::Point, gutter_dimensions: &GutterDimensions, gutter_hitbox: &Hitbox, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { let mut active = false; let mut button = None; @@ -1372,7 +1371,7 @@ impl EditorElement { active_rows: &BTreeMap, newest_selection_head: Option, snapshot: &EditorSnapshot, - cx: &ElementContext, + cx: &WindowContext, ) -> ( Vec>, Vec>, @@ -1465,7 +1464,7 @@ impl EditorElement { rows: Range, line_number_layouts: &[Option], snapshot: &EditorSnapshot, - cx: &ElementContext, + cx: &WindowContext, ) -> Vec { if rows.start >= rows.end { return Vec::new(); @@ -1530,7 +1529,7 @@ impl EditorElement { text_x: Pixels, line_height: Pixels, line_layouts: &[LineWithInvisibles], - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Vec { let mut block_id = 0; let (fixed_blocks, non_fixed_blocks) = snapshot @@ -1544,7 +1543,7 @@ impl EditorElement { available_space: Size, block_id: usize, block_row_start: u32, - cx: &mut ElementContext| { + cx: &mut WindowContext| { let mut element = match block { TransformBlock::Custom(block) => { let align_to = block @@ -1865,7 +1864,7 @@ impl EditorElement { hitbox: &Hitbox, line_height: Pixels, scroll_pixel_position: gpui::Point, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { for block in blocks { let mut origin = hitbox.origin @@ -1893,7 +1892,7 @@ impl EditorElement { scroll_pixel_position: gpui::Point, line_layouts: &[LineWithInvisibles], newest_selection_head: DisplayPoint, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> bool { let max_height = cmp::min( 12. * line_height, @@ -1933,7 +1932,7 @@ impl EditorElement { true } - fn layout_mouse_context_menu(&self, cx: &mut ElementContext) -> Option { + fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option { let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?; let mut element = deferred( anchored() @@ -1961,7 +1960,7 @@ impl EditorElement { line_layouts: &[LineWithInvisibles], line_height: Pixels, em_width: Pixels, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { struct MeasuredHoverPopover { element: AnyElement, @@ -2021,7 +2020,7 @@ impl EditorElement { } overall_height += HOVER_POPOVER_GAP; - fn draw_occluder(width: Pixels, origin: gpui::Point, cx: &mut ElementContext) { + fn draw_occluder(width: Pixels, origin: gpui::Point, cx: &mut WindowContext) { let mut occlusion = div() .size_full() .occlude() @@ -2067,7 +2066,7 @@ impl EditorElement { } } - fn paint_background(&self, layout: &EditorLayout, cx: &mut ElementContext) { + fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) { cx.paint_layer(layout.hitbox.bounds, |cx| { let scroll_top = layout.position_map.snapshot.scroll_position().y; let gutter_bg = cx.theme().colors().editor_gutter_background; @@ -2188,7 +2187,7 @@ impl EditorElement { }) } - fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { let line_height = layout.position_map.line_height; let scroll_position = layout.position_map.snapshot.scroll_position(); @@ -2236,7 +2235,7 @@ impl EditorElement { }) } - fn paint_diff_hunks(layout: &EditorLayout, cx: &mut ElementContext) { + fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) { if layout.display_hunks.is_empty() { return; } @@ -2342,7 +2341,7 @@ impl EditorElement { }) } - fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) { let Some(blamed_display_rows) = layout.blamed_display_rows.take() else { return; }; @@ -2354,7 +2353,7 @@ impl EditorElement { }) } - fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { cx.with_content_mask( Some(ContentMask { bounds: layout.text_hitbox.bounds, @@ -2386,7 +2385,7 @@ impl EditorElement { fn paint_highlights( &mut self, layout: &mut EditorLayout, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> SmallVec<[Range; 32]> { cx.paint_layer(layout.text_hitbox.bounds, |cx| { let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); @@ -2428,7 +2427,7 @@ impl EditorElement { &mut self, invisible_display_ranges: &[Range], layout: &EditorLayout, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let whitespace_setting = self .editor @@ -2451,7 +2450,7 @@ impl EditorElement { } } - fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut ElementContext) { + fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) { if layout.redacted_ranges.is_empty() { return; } @@ -2475,13 +2474,13 @@ impl EditorElement { }); } - fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { for cursor in &mut layout.cursors { cursor.paint(layout.content_origin, cx); } } - fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else { return; }; @@ -2617,7 +2616,7 @@ impl EditorElement { &self, layout: &EditorLayout, scrollbar_layout: &ScrollbarLayout, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.editor.update(cx, |editor, cx| { if !editor.is_singleton(cx) @@ -2775,7 +2774,7 @@ impl EditorElement { corner_radius: Pixels, line_end_overshoot: Pixels, layout: &EditorLayout, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let start_row = layout.visible_display_row_range.start; let end_row = layout.visible_display_row_range.end; @@ -2824,7 +2823,7 @@ impl EditorElement { } } - fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { if layout.folds.is_empty() { return; } @@ -2855,7 +2854,7 @@ impl EditorElement { }) } - fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { if let Some(mut inline_blame) = layout.inline_blame.take() { cx.paint_layer(layout.text_hitbox.bounds, |cx| { inline_blame.paint(cx); @@ -2863,19 +2862,19 @@ impl EditorElement { } } - fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { for mut block in layout.blocks.drain(..) { block.element.paint(cx); } } - fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) { + fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() { mouse_context_menu.paint(cx); } } - fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut ElementContext) { + fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut WindowContext) { cx.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); @@ -2925,7 +2924,7 @@ impl EditorElement { }); } - fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut ElementContext) { + fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut WindowContext) { self.paint_scroll_wheel_listener(layout, cx); cx.on_mouse_event({ @@ -3042,7 +3041,7 @@ fn render_inline_blame_entry( blame_entry: BlameEntry, style: &EditorStyle, workspace: Option>, - cx: &mut ElementContext<'_>, + cx: &mut WindowContext<'_>, ) -> AnyElement { let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx); @@ -3073,7 +3072,7 @@ fn render_blame_entry( style: &EditorStyle, last_used_color: &mut Option<(PlayerColor, Oid)>, editor: View, - cx: &mut ElementContext<'_>, + cx: &mut WindowContext<'_>, ) -> AnyElement { let mut sha_color = cx .theme() @@ -3286,7 +3285,7 @@ impl LineWithInvisibles { content_origin: gpui::Point, whitespace_setting: ShowWhitespaceSetting, selection_ranges: &[Range], - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let line_height = layout.position_map.line_height; let line_y = @@ -3318,7 +3317,7 @@ impl LineWithInvisibles { row: u32, line_height: Pixels, whitespace_setting: ShowWhitespaceSetting, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let allowed_invisibles_regions = match whitespace_setting { ShowWhitespaceSetting::None => return, @@ -3365,7 +3364,7 @@ impl Element for EditorElement { type RequestLayoutState = (); type PrepaintState = EditorLayout; - fn request_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, ()) { self.editor.update(cx, |editor, cx| { editor.set_style(self.style.clone(), cx); @@ -3375,36 +3374,31 @@ 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.with_element_context(|cx| cx.request_layout(&style, None)) + 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.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() - }, - ) + 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.with_element_context(|cx| cx.request_layout(&style, None)) + cx.request_layout(&style, None) } }; @@ -3416,7 +3410,7 @@ impl Element for EditorElement { &mut self, bounds: Bounds, _: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Self::PrepaintState { let text_style = TextStyleRefinement { font_size: Some(self.style.text.font_size), @@ -3847,13 +3841,12 @@ impl Element for EditorElement { bounds: Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let focus_handle = self.editor.focus_handle(cx); let key_context = self.editor.read(cx).key_context(cx); cx.set_focus_handle(&focus_handle); cx.set_key_context(key_context); - cx.set_view_id(self.editor.entity_id()); cx.handle_input( &focus_handle, ElementInputHandler::new(bounds, self.editor.clone()), @@ -4206,7 +4199,7 @@ impl CursorLayout { &mut self, origin: gpui::Point, cursor_name: Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { if let Some(cursor_name) = cursor_name { let bounds = self.bounds(origin); @@ -4236,7 +4229,7 @@ impl CursorLayout { } } - pub fn paint(&mut self, origin: gpui::Point, cx: &mut ElementContext) { + pub fn paint(&mut self, origin: gpui::Point, cx: &mut WindowContext) { let bounds = self.bounds(origin); //Draw background or border quad @@ -4280,7 +4273,7 @@ pub struct HighlightedRangeLine { } impl HighlightedRange { - pub fn paint(&self, bounds: Bounds, cx: &mut ElementContext) { + pub fn paint(&self, bounds: Bounds, cx: &mut WindowContext) { 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( @@ -4299,7 +4292,7 @@ impl HighlightedRange { start_y: Pixels, lines: &[HighlightedRangeLine], _bounds: Bounds, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { if lines.is_empty() { return; @@ -4416,7 +4409,7 @@ mod tests { editor_tests::{init_test, update_test_language_settings}, Editor, MultiBuffer, }; - use gpui::TestAppContext; + use gpui::{TestAppContext, VisualTestContext}; use language::language_settings; use log::info; use std::num::NonZeroU32; @@ -4437,18 +4430,16 @@ mod tests { let layouts = cx .update_window(*window, |_, cx| { - cx.with_element_context(|cx| { - element - .layout_line_numbers( - 0..6, - (0..6).map(Some), - &Default::default(), - Some(DisplayPoint::new(0, 0)), - &snapshot, - cx, - ) - .0 - }) + element + .layout_line_numbers( + 0..6, + (0..6).map(Some), + &Default::default(), + Some(DisplayPoint::new(0, 0)), + &snapshot, + cx, + ) + .0 }) .unwrap(); assert_eq!(layouts.len(), 6); @@ -4487,9 +4478,9 @@ mod tests { let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx); Editor::new(EditorMode::Full, buffer, None, cx) }); + let cx = &mut VisualTestContext::from_window(*window, cx); let editor = window.root(cx).unwrap(); let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); - let mut element = EditorElement::new(&editor, style); window .update(cx, |editor, cx| { @@ -4503,20 +4494,10 @@ mod tests { }); }) .unwrap(); - let state = cx - .update_window(window.into(), |_view, cx| { - cx.with_element_context(|cx| { - element.prepaint( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - &mut (), - cx, - ) - }) - }) - .unwrap(); + + let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| { + EditorElement::new(&editor, style) + }); assert_eq!(state.selections.len(), 1); let local_selections = &state.selections[0].1; @@ -4587,7 +4568,6 @@ mod tests { }); let editor = window.root(cx).unwrap(); let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); - let mut element = EditorElement::new(&editor, style); let _state = window.update(cx, |editor, cx| { editor.cursor_shape = CursorShape::Block; editor.change_selections(None, cx, |s| { @@ -4598,20 +4578,9 @@ mod tests { }); }); - let state = cx - .update_window(window.into(), |_view, cx| { - cx.with_element_context(|cx| { - element.prepaint( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - &mut (), - cx, - ) - }) - }) - .unwrap(); + let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| { + EditorElement::new(&editor, style) + }); assert_eq!(state.selections.len(), 1); let local_selections = &state.selections[0].1; assert_eq!(local_selections.len(), 2); @@ -4640,6 +4609,7 @@ mod tests { let buffer = MultiBuffer::build_simple("", cx); Editor::new(EditorMode::Full, buffer, None, cx) }); + let cx = &mut VisualTestContext::from_window(*window, cx); let editor = window.root(cx).unwrap(); let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); window @@ -4662,22 +4632,9 @@ mod tests { }) .unwrap(); - let mut element = EditorElement::new(&editor, style); - let state = cx - .update_window(window.into(), |_view, cx| { - cx.with_element_context(|cx| { - element.prepaint( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - &mut (), - cx, - ) - }) - }) - .unwrap(); - + let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| { + EditorElement::new(&editor, style) + }); assert_eq!(state.position_map.line_layouts.len(), 4); assert_eq!( state @@ -4850,31 +4807,19 @@ mod tests { let buffer = MultiBuffer::build_simple(&input_text, cx); Editor::new(editor_mode, buffer, None, cx) }); + let cx = &mut VisualTestContext::from_window(*window, cx); let editor = window.root(cx).unwrap(); let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); - let mut element = EditorElement::new(&editor, style); window .update(cx, |editor, cx| { editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); }) .unwrap(); - let layout_state = cx - .update_window(window.into(), |_, cx| { - cx.with_element_context(|cx| { - element.prepaint( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - &mut (), - cx, - ) - }) - }) - .unwrap(); - - layout_state + let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| { + EditorElement::new(&editor, style) + }); + state .position_map .line_layouts .iter() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 7a6f3b9e27..a17f8defe9 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,10 +1,11 @@ use crate::{ - Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - AvailableSpace, BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, Empty, - Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, - Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, - TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, + Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, AvailableSpace, + BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, DrawPhase, Drawable, + Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, + ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, + TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowContext, + WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{channel::oneshot, Stream, StreamExt}; @@ -725,21 +726,28 @@ impl VisualTestContext { } /// Draw an element to the window. Useful for simulating events or actions - pub fn draw( + pub fn draw( &mut self, origin: Point, - space: Size, - f: impl FnOnce(&mut WindowContext) -> AnyElement, - ) { + space: impl Into>, + f: impl FnOnce(&mut WindowContext) -> E, + ) -> (E::RequestLayoutState, E::PrepaintState) + where + E: Element, + { self.update(|cx| { - cx.with_element_context(|cx| { - let mut element = f(cx); - element.layout_as_root(space, cx); - cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx)); - element.paint(cx); - }); + cx.window.draw_phase = DrawPhase::Prepaint; + let mut element = Drawable::new(f(cx)); + element.layout_as_root(space.into(), cx); + cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx)); + cx.window.draw_phase = DrawPhase::Paint; + let (request_layout_state, prepaint_state) = element.paint(cx); + + cx.window.draw_phase = DrawPhase::None; cx.refresh(); + + (request_layout_state, prepaint_state) }) } diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index ddb728e437..c4079214dc 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -32,12 +32,12 @@ //! your own custom layout algorithm or rendering a code editor. use crate::{ - util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext, - ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, + util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementId, LayoutId, + Pixels, Point, Size, Style, ViewContext, WindowContext, ELEMENT_ARENA, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, mem, ops::DerefMut}; +use std::{any::Any, fmt::Debug, mem}; /// 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. @@ -54,7 +54,7 @@ pub trait Element: 'static + IntoElement { /// Before an element can be painted, we need to know where it's going to be and how big it is. /// Use this method to request a layout from Taffy and initialize the element's state. - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState); + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState); /// After laying out an element, we need to commit its bounds to the current frame for hitbox /// purposes. The state argument is the same state that was returned from [`Element::request_layout()`]. @@ -62,7 +62,7 @@ pub trait Element: 'static + IntoElement { &mut self, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Self::PrepaintState; /// Once layout has been completed, this method will be called to paint the element to the screen. @@ -72,7 +72,7 @@ pub trait Element: 'static + IntoElement { bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ); /// Convert this element into a dynamically-typed [`AnyElement`]. @@ -164,18 +164,13 @@ impl Element for Component { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { - let mut element = self - .0 - .take() - .unwrap() - .render(cx.deref_mut()) - .into_any_element(); + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + let mut element = self.0.take().unwrap().render(cx).into_any_element(); let layout_id = element.request_layout(cx); (layout_id, element) } - fn prepaint(&mut self, _: Bounds, element: &mut AnyElement, cx: &mut ElementContext) { + fn prepaint(&mut self, _: Bounds, element: &mut AnyElement, cx: &mut WindowContext) { element.prepaint(cx); } @@ -184,7 +179,7 @@ impl Element for Component { _: Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { element.paint(cx) } @@ -205,16 +200,16 @@ pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>); trait ElementObject { fn inner_element(&mut self) -> &mut dyn Any; - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId; + fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId; - fn prepaint(&mut self, cx: &mut ElementContext); + fn prepaint(&mut self, cx: &mut WindowContext); - fn paint(&mut self, cx: &mut ElementContext); + fn paint(&mut self, cx: &mut WindowContext); fn layout_as_root( &mut self, available_space: Size, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Size; } @@ -249,14 +244,14 @@ enum ElementDrawPhase { /// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. impl Drawable { - fn new(element: E) -> Self { + pub(crate) fn new(element: E) -> Self { Drawable { element, phase: ElementDrawPhase::Start, } } - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { + fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { match mem::take(&mut self.phase) { ElementDrawPhase::Start => { let (layout_id, request_layout) = self.element.request_layout(cx); @@ -270,7 +265,7 @@ impl Drawable { } } - fn prepaint(&mut self, cx: &mut ElementContext) { + pub(crate) fn prepaint(&mut self, cx: &mut WindowContext) { match mem::take(&mut self.phase) { ElementDrawPhase::RequestLayoutState { layout_id, @@ -296,7 +291,10 @@ impl Drawable { } } - fn paint(&mut self, cx: &mut ElementContext) -> E::RequestLayoutState { + pub(crate) fn paint( + &mut self, + cx: &mut WindowContext, + ) -> (E::RequestLayoutState, E::PrepaintState) { match mem::take(&mut self.phase) { ElementDrawPhase::PrepaintState { node_id, @@ -309,16 +307,16 @@ impl Drawable { self.element .paint(bounds, &mut request_layout, &mut prepaint, cx); self.phase = ElementDrawPhase::Painted; - request_layout + (request_layout, prepaint) } _ => panic!("must call prepaint before paint"), } } - fn layout_as_root( + pub(crate) fn layout_as_root( &mut self, available_space: Size, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Size { if matches!(&self.phase, ElementDrawPhase::Start) { self.request_layout(cx); @@ -368,22 +366,22 @@ where &mut self.element } - fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId { + fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { Drawable::request_layout(self, cx) } - fn prepaint(&mut self, cx: &mut ElementContext) { + fn prepaint(&mut self, cx: &mut WindowContext) { Drawable::prepaint(self, cx); } - fn paint(&mut self, cx: &mut ElementContext) { + fn paint(&mut self, cx: &mut WindowContext) { Drawable::paint(self, cx); } fn layout_as_root( &mut self, available_space: Size, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Size { Drawable::layout_as_root(self, available_space, cx) } @@ -411,18 +409,18 @@ 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 ElementContext) -> LayoutId { + pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId { self.0.request_layout(cx) } /// Prepares the element to be painted by storing its bounds, giving it a chance to draw hitboxes and /// request autoscroll before the final paint pass is confirmed. - pub fn prepaint(&mut self, cx: &mut ElementContext) { + pub fn prepaint(&mut self, cx: &mut WindowContext) { self.0.prepaint(cx) } /// Paints the element stored in this `AnyElement`. - pub fn paint(&mut self, cx: &mut ElementContext) { + pub fn paint(&mut self, cx: &mut WindowContext) { self.0.paint(cx) } @@ -430,13 +428,13 @@ impl AnyElement { pub fn layout_as_root( &mut self, available_space: Size, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Size { self.0.layout_as_root(available_space, cx) } /// Prepaints this element at the given absolute origin. - pub fn prepaint_at(&mut self, origin: Point, cx: &mut ElementContext) { + pub fn prepaint_at(&mut self, origin: Point, cx: &mut WindowContext) { cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx)); } @@ -445,7 +443,7 @@ impl AnyElement { &mut self, origin: Point, available_space: Size, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.layout_as_root(available_space, cx); cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx)); @@ -456,7 +454,7 @@ impl Element for AnyElement { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self.request_layout(cx); (layout_id, ()) } @@ -465,7 +463,7 @@ impl Element for AnyElement { &mut self, _: Bounds, _: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.prepaint(cx) } @@ -475,7 +473,7 @@ impl Element for AnyElement { _: Bounds, _: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.paint(cx) } @@ -508,15 +506,15 @@ impl Element for Empty { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { - (cx.request_layout(&crate::Style::default(), None), ()) + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { + (cx.request_layout(&Style::default(), None), ()) } fn prepaint( &mut self, _bounds: Bounds, _state: &mut Self::RequestLayoutState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } @@ -525,7 +523,7 @@ impl Element for Empty { _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } } diff --git a/crates/gpui/src/elements/anchored.rs b/crates/gpui/src/elements/anchored.rs index 9f4d342716..15421f4ab3 100644 --- a/crates/gpui/src/elements/anchored.rs +++ b/crates/gpui/src/elements/anchored.rs @@ -2,8 +2,8 @@ use smallvec::SmallVec; use taffy::style::{Display, Position}; use crate::{ - point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement, - Pixels, Point, Size, Style, + point, AnyElement, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, Point, Size, + Style, WindowContext, }; /// The state that the anchored element element uses to track its children. @@ -74,7 +74,7 @@ impl Element for Anchored { fn request_layout( &mut self, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let child_layout_ids = self .children @@ -97,7 +97,7 @@ impl Element for Anchored { &mut self, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { if request_layout.child_layout_ids.is_empty() { return; @@ -180,7 +180,7 @@ impl Element for Anchored { _bounds: crate::Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { for child in &mut self.children { child.paint(cx); diff --git a/crates/gpui/src/elements/animation.rs b/crates/gpui/src/elements/animation.rs index 586b26f7e9..f18ff3fcb8 100644 --- a/crates/gpui/src/elements/animation.rs +++ b/crates/gpui/src/elements/animation.rs @@ -91,7 +91,7 @@ impl Element for AnimationElement { fn request_layout( &mut self, - cx: &mut crate::ElementContext, + cx: &mut crate::WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { cx.with_element_state(Some(self.id.clone()), |state, cx| { let state = state.unwrap().unwrap_or_else(|| AnimationState { @@ -138,7 +138,7 @@ impl Element for AnimationElement { &mut self, _bounds: crate::Bounds, element: &mut Self::RequestLayoutState, - cx: &mut crate::ElementContext, + cx: &mut crate::WindowContext, ) -> Self::PrepaintState { element.prepaint(cx); } @@ -148,7 +148,7 @@ impl Element for AnimationElement { _bounds: crate::Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut crate::ElementContext, + cx: &mut crate::WindowContext, ) { element.paint(cx); } diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index c0bfc044ab..989ea76da5 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -1,12 +1,12 @@ use refineable::Refineable as _; -use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled}; +use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext}; /// Construct a canvas element with the given paint callback. /// Useful for adding short term custom drawing to a view. pub fn canvas( - prepaint: impl 'static + FnOnce(Bounds, &mut ElementContext) -> T, - paint: impl 'static + FnOnce(Bounds, T, &mut ElementContext), + prepaint: impl 'static + FnOnce(Bounds, &mut WindowContext) -> T, + paint: impl 'static + FnOnce(Bounds, T, &mut WindowContext), ) -> Canvas { Canvas { prepaint: Some(Box::new(prepaint)), @@ -18,8 +18,8 @@ pub fn canvas( /// A canvas element, meant for accessing the low level paint API without defining a whole /// custom element pub struct Canvas { - prepaint: Option, &mut ElementContext) -> T>>, - paint: Option, T, &mut ElementContext)>>, + prepaint: Option, &mut WindowContext) -> T>>, + paint: Option, T, &mut WindowContext)>>, style: StyleRefinement, } @@ -37,7 +37,7 @@ impl Element for Canvas { fn request_layout( &mut self, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); style.refine(&self.style); @@ -49,7 +49,7 @@ impl Element for Canvas { &mut self, bounds: Bounds, _request_layout: &mut Style, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { Some(self.prepaint.take().unwrap()(bounds, cx)) } @@ -59,7 +59,7 @@ impl Element for Canvas { bounds: Bounds, style: &mut Style, prepaint: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let prepaint = prepaint.take().unwrap(); style.paint(bounds, cx, |cx| { diff --git a/crates/gpui/src/elements/deferred.rs b/crates/gpui/src/elements/deferred.rs index 30643bdc2a..9bf365ae0d 100644 --- a/crates/gpui/src/elements/deferred.rs +++ b/crates/gpui/src/elements/deferred.rs @@ -1,4 +1,4 @@ -use crate::{AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, Pixels}; +use crate::{AnyElement, Bounds, Element, IntoElement, LayoutId, Pixels, WindowContext}; /// Builds a `Deferred` element, which delays the layout and paint of its child. pub fn deferred(child: impl IntoElement) -> Deferred { @@ -29,7 +29,7 @@ impl Element for Deferred { type RequestLayoutState = (); type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, ()) { let layout_id = self.child.as_mut().unwrap().request_layout(cx); (layout_id, ()) } @@ -38,7 +38,7 @@ impl Element for Deferred { &mut self, _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let child = self.child.take().unwrap(); let element_offset = cx.element_offset(); @@ -50,7 +50,7 @@ impl Element for Deferred { _bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, _prepaint: &mut Self::PrepaintState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 92c3420206..981b86aadc 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -17,11 +17,11 @@ use crate::{ point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds, - ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, Hitbox, - HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, - StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext, + ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, Hitbox, HitboxId, + IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ModifiersChangedEvent, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, + Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId, + View, Visibility, WindowContext, }; use collections::HashMap; use refineable::Refineable; @@ -1123,7 +1123,7 @@ impl Element for Div { type RequestLayoutState = DivFrameState; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let mut child_layout_ids = SmallVec::new(); let layout_id = self.interactivity.request_layout(cx, |style, cx| { cx.with_text_style(style.text_style().cloned(), |cx| { @@ -1142,7 +1142,7 @@ impl Element for Div { &mut self, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); @@ -1197,7 +1197,7 @@ impl Element for Div { bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.interactivity .paint(bounds, hitbox.as_ref(), cx, |_style, cx| { @@ -1276,8 +1276,8 @@ impl Interactivity { /// Layout this element according to this interactivity state's configured styles pub fn request_layout( &mut self, - cx: &mut ElementContext, - f: impl FnOnce(Style, &mut ElementContext) -> LayoutId, + cx: &mut WindowContext, + f: impl FnOnce(Style, &mut WindowContext) -> LayoutId, ) -> LayoutId { cx.with_element_state::( self.element_id.clone(), @@ -1341,8 +1341,8 @@ impl Interactivity { &mut self, bounds: Bounds, content_size: Size, - cx: &mut ElementContext, - f: impl FnOnce(&Style, Point, Option, &mut ElementContext) -> R, + cx: &mut WindowContext, + f: impl FnOnce(&Style, Point, Option, &mut WindowContext) -> R, ) -> R { self.content_size = content_size; cx.with_element_state::( @@ -1406,7 +1406,7 @@ impl Interactivity { &mut self, bounds: Bounds, style: &Style, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Point { if let Some(scroll_offset) = self.scroll_offset.as_ref() { if let Some(scroll_handle) = &self.tracked_scroll_handle { @@ -1456,8 +1456,8 @@ impl Interactivity { &mut self, bounds: Bounds, hitbox: Option<&Hitbox>, - cx: &mut ElementContext, - f: impl FnOnce(&Style, &mut ElementContext), + cx: &mut WindowContext, + f: impl FnOnce(&Style, &mut WindowContext), ) { self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(cx)); cx.with_element_state::( @@ -1482,7 +1482,7 @@ impl Interactivity { return ((), element_state); } - style.paint(bounds, cx, |cx: &mut ElementContext| { + style.paint(bounds, cx, |cx: &mut WindowContext| { cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| { if let Some(hitbox) = hitbox { @@ -1521,7 +1521,7 @@ impl Interactivity { } #[cfg(debug_assertions)] - fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) { + fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) { if self.element_id.is_some() && (style.debug || style.debug_below || cx.has_global::()) && hitbox.is_hovered(cx) @@ -1530,7 +1530,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 ElementContext| { + let render_debug_text = |cx: &mut WindowContext| { if let Some(text) = cx .text_system() .shape_text( @@ -1629,7 +1629,7 @@ impl Interactivity { &mut self, hitbox: &Hitbox, element_state: Option<&mut InteractiveElementState>, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { // If this element can be focused, register a mouse down listener // that will automatically transfer focus when hitting the element. @@ -1712,11 +1712,11 @@ impl Interactivity { let mut can_drop = true; if let Some(predicate) = &can_drop_predicate { - can_drop = predicate(drag.value.as_ref(), cx.deref_mut()); + can_drop = predicate(drag.value.as_ref(), cx); } if can_drop { - listener(drag.value.as_ref(), cx.deref_mut()); + listener(drag.value.as_ref(), cx); cx.refresh(); cx.stop_propagation(); } @@ -1840,7 +1840,7 @@ impl Interactivity { *was_hovered = is_hovered; drop(was_hovered); - hover_listener(&is_hovered, cx.deref_mut()); + hover_listener(&is_hovered, cx); } }); } @@ -1969,7 +1969,7 @@ impl Interactivity { } } - fn paint_keyboard_listeners(&mut self, cx: &mut ElementContext) { + fn paint_keyboard_listeners(&mut self, cx: &mut WindowContext) { let key_down_listeners = mem::take(&mut self.key_down_listeners); let key_up_listeners = mem::take(&mut self.key_up_listeners); let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners); @@ -2004,7 +2004,7 @@ impl Interactivity { } } - fn paint_hover_group_handler(&self, cx: &mut ElementContext) { + fn paint_hover_group_handler(&self, cx: &mut WindowContext) { let group_hitbox = self .group_hover_style .as_ref() @@ -2021,7 +2021,7 @@ impl Interactivity { } } - fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) { + fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) { if let Some(scroll_offset) = self.scroll_offset.clone() { let overflow = style.overflow; let line_height = cx.line_height(); @@ -2064,7 +2064,7 @@ impl Interactivity { } /// Compute the visual style for this element, based on the current bounds and the element's state. - pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut ElementContext) -> Style { + pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut WindowContext) -> Style { cx.with_element_state(self.element_id.clone(), |element_state, cx| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); @@ -2078,7 +2078,7 @@ impl Interactivity { &self, hitbox: Option<&Hitbox>, element_state: Option<&mut InteractiveElementState>, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Style { let mut style = Style::default(); style.refine(&self.base_style); @@ -2119,7 +2119,7 @@ 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.deref_mut()); + can_drop = can_drop_predicate(drag.value.as_ref(), cx); } if can_drop { @@ -2264,7 +2264,7 @@ where type RequestLayoutState = E::RequestLayoutState; type PrepaintState = E::PrepaintState; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { self.element.request_layout(cx) } @@ -2272,7 +2272,7 @@ where &mut self, bounds: Bounds, state: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> E::PrepaintState { self.element.prepaint(bounds, state, cx) } @@ -2282,7 +2282,7 @@ where bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.element.paint(bounds, request_layout, prepaint, cx) } @@ -2347,7 +2347,7 @@ where type RequestLayoutState = E::RequestLayoutState; type PrepaintState = E::PrepaintState; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { self.element.request_layout(cx) } @@ -2355,7 +2355,7 @@ where &mut self, bounds: Bounds, state: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> E::PrepaintState { self.element.prepaint(bounds, state, cx) } @@ -2365,7 +2365,7 @@ where bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.element.paint(bounds, request_layout, prepaint, cx); } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index fad4a49fee..51eeccb3f8 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -3,9 +3,9 @@ use std::path::PathBuf; use std::sync::Arc; use crate::{ - point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, - ElementContext, Hitbox, ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId, - Length, Pixels, SharedUri, Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext, + point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, Hitbox, + ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, Pixels, SharedUri, + Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext, }; use futures::{AsyncReadExt, Future}; use image::{ImageBuffer, ImageError}; @@ -232,7 +232,7 @@ impl Element for Img { type RequestLayoutState = (); type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self.interactivity.request_layout(cx, |mut style, cx| { if let Some(data) = self.source.data(cx) { let image_size = data.size(); @@ -260,7 +260,7 @@ impl Element for Img { &mut self, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { self.interactivity .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) @@ -271,7 +271,7 @@ impl Element for Img { bounds: Bounds, _: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let source = self.source.clone(); self.interactivity @@ -319,7 +319,7 @@ impl InteractiveElement for Img { } impl ImageSource { - fn data(&self, cx: &mut ElementContext) -> Option> { + fn data(&self, cx: &mut WindowContext) -> Option> { match self { ImageSource::Uri(_) | ImageSource::File(_) => { let uri_or_path: UriOrPath = match self { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index d5caf22955..befee0bbd9 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -8,8 +8,8 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, - Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, - Size, Style, StyleRefinement, Styled, WindowContext, + Element, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, + StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; @@ -434,7 +434,7 @@ impl StateInner { available_width: Option, available_height: Pixels, padding: &Edges, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> LayoutItemsResponse { let old_items = self.items.clone(); let mut measured_items = VecDeque::new(); @@ -609,7 +609,7 @@ impl StateInner { bounds: Bounds, padding: Edges, autoscroll: bool, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Result { cx.transact(|cx| { let mut layout_response = @@ -706,7 +706,7 @@ impl Element for List { fn request_layout( &mut self, - cx: &mut crate::ElementContext, + cx: &mut crate::WindowContext, ) -> (crate::LayoutId, Self::RequestLayoutState) { let layout_id = match self.sizing_behavior { ListSizingBehavior::Infer => { @@ -772,7 +772,7 @@ impl Element for List { &mut self, bounds: Bounds, _: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> ListPrepaintState { let state = &mut *self.state.0.borrow_mut(); state.reset = false; @@ -815,7 +815,7 @@ impl Element for List { bounds: Bounds, _: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, - cx: &mut crate::ElementContext, + cx: &mut crate::WindowContext, ) { cx.with_content_mask(Some(ContentMask { bounds }), |cx| { for item in &mut prepaint.layout.item_layouts { @@ -951,11 +951,9 @@ mod test { }); // Paint - cx.draw( - point(px(0.), px(0.)), - size(px(100.), px(20.)).into(), - |_| list(state.clone()).w_full().h_full().into_any(), - ); + cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_| { + list(state.clone()).w_full().h_full() + }); // Reset state.reset(5); diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index ae2f4c2074..83f9ba68df 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,7 +1,7 @@ use crate::{ - geometry::Negate as _, point, px, radians, size, Bounds, Element, ElementContext, Hitbox, - InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, - Size, StyleRefinement, Styled, TransformationMatrix, + geometry::Negate as _, point, px, radians, size, Bounds, Element, Hitbox, InteractiveElement, + Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size, + StyleRefinement, Styled, TransformationMatrix, WindowContext, }; use util::ResultExt; @@ -40,7 +40,7 @@ impl Element for Svg { type RequestLayoutState = (); type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let layout_id = self .interactivity .request_layout(cx, |style, cx| cx.request_layout(&style, None)); @@ -51,7 +51,7 @@ impl Element for Svg { &mut self, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { self.interactivity .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) @@ -62,7 +62,7 @@ impl Element for Svg { bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) where Self: Sized, { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 565638bfb4..bdafd809a8 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -1,8 +1,7 @@ use crate::{ - ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId, - HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, - TOOLTIP_DELAY, + ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle, + Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, + SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -20,7 +19,7 @@ impl Element for &'static str { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(SharedString::from(*self), None, cx); (layout_id, state) @@ -30,7 +29,7 @@ impl Element for &'static str { &mut self, _bounds: Bounds, _text_state: &mut Self::RequestLayoutState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } @@ -39,7 +38,7 @@ impl Element for &'static str { bounds: Bounds, text_state: &mut TextState, _: &mut (), - cx: &mut ElementContext, + cx: &mut WindowContext, ) { text_state.paint(bounds, self, cx) } @@ -65,7 +64,7 @@ impl Element for SharedString { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(self.clone(), None, cx); (layout_id, state) @@ -75,7 +74,7 @@ impl Element for SharedString { &mut self, _bounds: Bounds, _text_state: &mut Self::RequestLayoutState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } @@ -84,7 +83,7 @@ impl Element for SharedString { bounds: Bounds, text_state: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let text_str: &str = self.as_ref(); text_state.paint(bounds, text_str, cx) @@ -151,7 +150,7 @@ impl Element for StyledText { type RequestLayoutState = TextState; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let mut state = TextState::default(); let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); (layout_id, state) @@ -161,7 +160,7 @@ impl Element for StyledText { &mut self, _bounds: Bounds, _state: &mut Self::RequestLayoutState, - _cx: &mut ElementContext, + _cx: &mut WindowContext, ) { } @@ -170,7 +169,7 @@ impl Element for StyledText { bounds: Bounds, text_state: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { text_state.paint(bounds, &self.text, cx) } @@ -204,7 +203,7 @@ impl TextState { &mut self, text: SharedString, runs: Option>, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> LayoutId { let text_style = cx.text_style(); let font_size = text_style.font_size.to_pixels(cx.rem_size()); @@ -279,7 +278,7 @@ impl TextState { layout_id } - fn paint(&mut self, bounds: Bounds, text: &str, cx: &mut ElementContext) { + fn paint(&mut self, bounds: Bounds, text: &str, cx: &mut WindowContext) { let element_state = self.lock(); let element_state = element_state .as_ref() @@ -405,7 +404,7 @@ impl Element for InteractiveText { type RequestLayoutState = TextState; type PrepaintState = Hitbox; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { self.text.request_layout(cx) } @@ -413,7 +412,7 @@ impl Element for InteractiveText { &mut self, bounds: Bounds, state: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Hitbox { cx.with_element_state::( Some(self.element_id.clone()), @@ -442,7 +441,7 @@ impl Element for InteractiveText { bounds: Bounds, text_state: &mut Self::RequestLayoutState, hitbox: &mut Hitbox, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { cx.with_element_state::( Some(self.element_id.clone()), diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 2813043c7a..910f82bbee 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,9 +5,9 @@ //! elements with uniform height. use crate::{ - point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext, - ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, - ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId, Hitbox, + InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, ScrollHandle, Size, + StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -107,7 +107,7 @@ impl Element for UniformList { type RequestLayoutState = UniformListFrameState; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let max_items = self.item_count; let item_size = self.measure_item(None, cx); let layout_id = self.interactivity.request_layout(cx, |style, cx| { @@ -141,7 +141,7 @@ impl Element for UniformList { &mut self, bounds: Bounds, frame_state: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { let style = self.interactivity.compute_style(None, cx); let border = style.border_widths.to_pixels(cx.rem_size()); @@ -239,7 +239,7 @@ impl Element for UniformList { bounds: Bounds, request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.interactivity .paint(bounds, hitbox.as_ref(), cx, |_, cx| { @@ -265,7 +265,7 @@ impl UniformList { self } - fn measure_item(&self, list_width: Option, cx: &mut ElementContext) -> Size { + fn measure_item(&self, list_width: Option, cx: &mut WindowContext) -> Size { if self.item_count == 0 { return Size::default(); } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index ff0b995693..0031ed82c2 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -50,9 +50,8 @@ /// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane")) /// use crate::{ - Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding, - KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent, - WindowContext, + Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap, + KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent, WindowContext, }; use collections::FxHashMap; use smallvec::SmallVec; @@ -107,8 +106,8 @@ impl ReusedSubtree { } } -type KeyListener = Rc; -type ModifiersChangedListener = Rc; +type KeyListener = Rc; +type ModifiersChangedListener = Rc; #[derive(Clone)] pub(crate) struct DispatchActionListener { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 9a002d6700..4a7c78d751 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -2,9 +2,9 @@ use std::{iter, mem, ops::Range}; use crate::{ black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement, - CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font, FontFeatures, - FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, - SizeRefinement, Styled, TextRun, + CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, + Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled, + TextRun, WindowContext, }; use collections::HashSet; use refineable::Refineable; @@ -391,8 +391,8 @@ impl Style { pub fn paint( &self, bounds: Bounds, - cx: &mut ElementContext, - continuation: impl FnOnce(&mut ElementContext), + cx: &mut WindowContext, + continuation: impl FnOnce(&mut WindowContext), ) { #[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 855cfaf37e..a9a52f0757 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, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result, - SharedString, StrikethroughStyle, UnderlineStyle, WrapBoundary, WrappedLineLayout, + black, fill, point, px, size, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString, + StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout, }; use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; @@ -48,7 +48,7 @@ impl ShapedLine { &self, origin: Point, line_height: Pixels, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Result<()> { paint_line( origin, @@ -86,7 +86,7 @@ impl WrappedLine { &self, origin: Point, line_height: Pixels, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Result<()> { paint_line( origin, @@ -107,7 +107,7 @@ fn paint_line( line_height: Pixels, decoration_runs: &[DecorationRun], wrap_boundaries: &[WrapBoundary], - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Result<()> { let line_bounds = Bounds::new(origin, size(layout.width, line_height)); cx.paint_layer(line_bounds, |cx| { diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 3d9fb82cd5..6b68e5235e 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, Bounds, ContentMask, Element, - ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, - LayoutId, Model, PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, - TextStyle, ViewContext, VisualContext, WeakModel, + ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, LayoutId, Model, + PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, ViewContext, + VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use refineable::Refineable; @@ -93,7 +93,7 @@ impl Element for View { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); let layout_id = element.request_layout(cx); @@ -105,7 +105,7 @@ impl Element for View { &mut self, _: Bounds, element: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { cx.set_view_id(self.entity_id()); cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { @@ -118,7 +118,7 @@ impl Element for View { _: Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| { element.paint(cx) @@ -220,7 +220,7 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - render: fn(&AnyView, &mut ElementContext) -> AnyElement, + render: fn(&AnyView, &mut WindowContext) -> AnyElement, cached_style: Option, } @@ -279,7 +279,7 @@ impl Element for AnyView { type RequestLayoutState = Option; type PrepaintState = Option; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { if let Some(style) = self.cached_style.as_ref() { let mut root_style = Style::default(); root_style.refine(style); @@ -298,7 +298,7 @@ impl Element for AnyView { &mut self, bounds: Bounds, element: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { cx.set_view_id(self.entity_id()); if self.cached_style.is_some() { @@ -359,7 +359,7 @@ impl Element for AnyView { _bounds: Bounds, _: &mut Self::RequestLayoutState, element: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { if self.cached_style.is_some() { cx.with_element_state::( @@ -408,7 +408,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, - render: fn(&AnyView, &mut ElementContext) -> AnyElement, + render: fn(&AnyView, &mut WindowContext) -> AnyElement, } impl AnyWeakView { @@ -447,11 +447,11 @@ impl std::fmt::Debug for AnyWeakView { } mod any_view { - use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render}; + use crate::{AnyElement, AnyView, IntoElement, Render, WindowContext}; pub(crate) fn render( view: &AnyView, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> AnyElement { let view = view.clone().downcast::().unwrap(); 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 3c6160515f..4d20b4a441 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,32 +1,42 @@ use crate::{ - point, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, - AsyncWindowContext, Bounds, Context, Corners, CursorStyle, DevicePixels, - DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId, Hsla, KeyBinding, - KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, - Modifiers, ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, - PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, - ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, - TextStyle, TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, - WindowBackgroundAppearance, WindowOptions, WindowParams, WindowTextSystem, + hash, point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip, + AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, + Context, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId, + DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, + FontId, Global, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyBinding, + KeyContext, KeyDownEvent, KeyEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, + LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers, ModifiersChangedEvent, + 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, StrikethroughStyle, Style, + SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, + TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView, + WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams, WindowTextSystem, + SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::FxHashSet; +use collections::{FxHashMap, FxHashSet}; use derive_more::{Deref, DerefMut}; use futures::channel::oneshot; +use futures::{future::Shared, FutureExt}; +#[cfg(target_os = "macos")] +use media::core_video::CVImageBuffer; use parking_lot::RwLock; use refineable::Refineable; use slotmap::SlotMap; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, - borrow::{Borrow, BorrowMut}, + borrow::{Borrow, BorrowMut, Cow}, cell::{Cell, RefCell}, + cmp, fmt::{Debug, Display}, future::Future, hash::{Hash, Hasher}, marker::PhantomData, mem, + ops::Range, rc::Rc, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, @@ -34,12 +44,11 @@ use std::{ }, time::{Duration, Instant}, }; +use util::post_inc; use util::{measure, ResultExt}; -mod element_cx; mod prompts; -pub use element_cx::*; pub use prompts::*; /// Represents the two different phases when dispatching events. @@ -269,6 +278,192 @@ pub struct DismissEvent; type FrameCallback = Box; +pub(crate) type AnyMouseListener = + Box; + +#[derive(Clone)] +pub(crate) struct CursorStyleRequest { + pub(crate) hitbox_id: HitboxId, + pub(crate) style: CursorStyle, +} + +/// An identifier for a [Hitbox]. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct HitboxId(usize); + +impl HitboxId { + /// Checks if the hitbox with this id is currently hovered. + pub fn is_hovered(&self, cx: &WindowContext) -> bool { + cx.window.mouse_hit_test.0.contains(self) + } +} + +/// A rectangular region that potentially blocks hitboxes inserted prior. +/// See [WindowContext::insert_hitbox] for more details. +#[derive(Clone, Debug, Deref)] +pub struct Hitbox { + /// A unique identifier for the hitbox. + pub id: HitboxId, + /// The bounds of the hitbox. + #[deref] + pub bounds: Bounds, + /// The content mask when the hitbox was inserted. + pub content_mask: ContentMask, + /// Whether the hitbox occludes other hitboxes inserted prior. + pub opaque: bool, +} + +impl Hitbox { + /// Checks if the hitbox is currently hovered. + pub fn is_hovered(&self, cx: &WindowContext) -> bool { + self.id.is_hovered(cx) + } +} + +#[derive(Default, Eq, PartialEq)] +pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>); + +/// An identifier for a tooltip. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct TooltipId(usize); + +impl TooltipId { + /// Checks if the tooltip is currently hovered. + pub fn is_hovered(&self, cx: &WindowContext) -> bool { + cx.window + .tooltip_bounds + .as_ref() + .map_or(false, |tooltip_bounds| { + tooltip_bounds.id == *self && tooltip_bounds.bounds.contains(&cx.mouse_position()) + }) + } +} + +pub(crate) struct TooltipBounds { + id: TooltipId, + bounds: Bounds, +} + +#[derive(Clone)] +pub(crate) struct TooltipRequest { + id: TooltipId, + tooltip: AnyTooltip, +} + +pub(crate) struct DeferredDraw { + priority: usize, + parent_node: DispatchNodeId, + element_id_stack: GlobalElementId, + text_style_stack: Vec, + element: Option, + absolute_offset: Point, + prepaint_range: Range, + paint_range: Range, +} + +pub(crate) struct Frame { + pub(crate) focus: Option, + pub(crate) window_active: bool, + pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>, + accessed_element_states: Vec<(GlobalElementId, TypeId)>, + pub(crate) mouse_listeners: Vec>, + pub(crate) dispatch_tree: DispatchTree, + pub(crate) scene: Scene, + pub(crate) hitboxes: Vec, + pub(crate) deferred_draws: Vec, + pub(crate) input_handlers: Vec>, + pub(crate) tooltip_requests: Vec>, + pub(crate) cursor_styles: Vec, + #[cfg(any(test, feature = "test-support"))] + pub(crate) debug_bounds: FxHashMap>, +} + +#[derive(Clone, Default)] +pub(crate) struct PrepaintStateIndex { + hitboxes_index: usize, + tooltips_index: usize, + deferred_draws_index: usize, + dispatch_tree_index: usize, + accessed_element_states_index: usize, + line_layout_index: LineLayoutIndex, +} + +#[derive(Clone, Default)] +pub(crate) struct PaintIndex { + scene_index: usize, + mouse_listeners_index: usize, + input_handlers_index: usize, + cursor_styles_index: usize, + accessed_element_states_index: usize, + line_layout_index: LineLayoutIndex, +} + +impl Frame { + pub(crate) fn new(dispatch_tree: DispatchTree) -> Self { + Frame { + focus: None, + window_active: false, + element_states: FxHashMap::default(), + accessed_element_states: Vec::new(), + mouse_listeners: Vec::new(), + dispatch_tree, + scene: Scene::default(), + hitboxes: Vec::new(), + deferred_draws: Vec::new(), + input_handlers: Vec::new(), + tooltip_requests: Vec::new(), + cursor_styles: Vec::new(), + + #[cfg(any(test, feature = "test-support"))] + debug_bounds: FxHashMap::default(), + } + } + + pub(crate) fn clear(&mut self) { + self.element_states.clear(); + self.accessed_element_states.clear(); + self.mouse_listeners.clear(); + self.dispatch_tree.clear(); + self.scene.clear(); + self.input_handlers.clear(); + self.tooltip_requests.clear(); + self.cursor_styles.clear(); + self.hitboxes.clear(); + self.deferred_draws.clear(); + } + + pub(crate) fn hit_test(&self, position: Point) -> HitTest { + let mut hit_test = HitTest::default(); + for hitbox in self.hitboxes.iter().rev() { + let bounds = hitbox.bounds.intersect(&hitbox.content_mask.bounds); + if bounds.contains(&position) { + hit_test.0.push(hitbox.id); + if hitbox.opaque { + break; + } + } + } + hit_test + } + + 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) { + for element_state_key in &self.accessed_element_states { + if let Some(element_state) = prev_frame.element_states.remove(element_state_key) { + self.element_states + .insert(element_state_key.clone(), element_state); + } + } + + self.scene.finish(); + } +} + // Holds the state for a specific window. #[doc(hidden)] pub struct Window { @@ -321,7 +516,7 @@ pub struct Window { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum DrawPhase { None, - Layout, + Prepaint, Paint, Focus, } @@ -1039,7 +1234,7 @@ impl<'a> WindowContext<'a> { .push(Some(input_handler)); } - self.with_element_context(|cx| cx.draw_roots()); + self.draw_roots(); self.window.dirty_views.clear(); self.window @@ -1123,6 +1318,1431 @@ impl<'a> WindowContext<'a> { profiling::finish_frame!(); } + fn draw_roots(&mut self) { + self.window.draw_phase = DrawPhase::Prepaint; + self.window.tooltip_bounds.take(); + + // Layout all root elements. + let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any(); + root_element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self); + + let mut sorted_deferred_draws = + (0..self.window.next_frame.deferred_draws.len()).collect::>(); + sorted_deferred_draws.sort_by_key(|ix| self.window.next_frame.deferred_draws[*ix].priority); + self.prepaint_deferred_draws(&sorted_deferred_draws); + + let mut prompt_element = None; + let mut active_drag_element = None; + let mut tooltip_element = None; + if let Some(prompt) = self.window.prompt.take() { + let mut element = prompt.view.any_view().into_any(); + element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self); + prompt_element = Some(element); + self.window.prompt = Some(prompt); + } else if let Some(active_drag) = self.app.active_drag.take() { + let mut element = active_drag.view.clone().into_any(); + let offset = self.mouse_position() - active_drag.cursor_offset; + element.prepaint_as_root(offset, AvailableSpace::min_size(), self); + active_drag_element = Some(element); + self.app.active_drag = Some(active_drag); + } else { + tooltip_element = self.prepaint_tooltip(); + } + + self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position); + + // Now actually paint the elements. + self.window.draw_phase = DrawPhase::Paint; + root_element.paint(self); + + self.paint_deferred_draws(&sorted_deferred_draws); + + if let Some(mut prompt_element) = prompt_element { + prompt_element.paint(self) + } else if let Some(mut drag_element) = active_drag_element { + drag_element.paint(self); + } else if let Some(mut tooltip_element) = tooltip_element { + tooltip_element.paint(self); + } + } + + fn prepaint_tooltip(&mut self) -> Option { + let tooltip_request = self.window.next_frame.tooltip_requests.last().cloned()?; + let tooltip_request = tooltip_request.unwrap(); + let mut element = tooltip_request.tooltip.view.clone().into_any(); + let mouse_position = tooltip_request.tooltip.mouse_position; + let tooltip_size = element.layout_as_root(AvailableSpace::min_size(), self); + + let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size); + let window_bounds = Bounds { + origin: Point::default(), + size: self.viewport_size(), + }; + + if tooltip_bounds.right() > window_bounds.right() { + let new_x = mouse_position.x - tooltip_bounds.size.width - px(1.); + if new_x >= Pixels::ZERO { + tooltip_bounds.origin.x = new_x; + } else { + tooltip_bounds.origin.x = cmp::max( + Pixels::ZERO, + tooltip_bounds.origin.x - tooltip_bounds.right() - window_bounds.right(), + ); + } + } + + if tooltip_bounds.bottom() > window_bounds.bottom() { + let new_y = mouse_position.y - tooltip_bounds.size.height - px(1.); + if new_y >= Pixels::ZERO { + tooltip_bounds.origin.y = new_y; + } else { + tooltip_bounds.origin.y = cmp::max( + Pixels::ZERO, + tooltip_bounds.origin.y - tooltip_bounds.bottom() - window_bounds.bottom(), + ); + } + } + + self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx)); + + self.window.tooltip_bounds = Some(TooltipBounds { + id: tooltip_request.id, + bounds: tooltip_bounds, + }); + Some(element) + } + + fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { + assert_eq!(self.window.element_id_stack.len(), 0); + + let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); + for deferred_draw_ix in deferred_draw_indices { + let deferred_draw = &mut deferred_draws[*deferred_draw_ix]; + self.window.element_id_stack = deferred_draw.element_id_stack.clone(); + self.window.text_style_stack = deferred_draw.text_style_stack.clone(); + self.window + .next_frame + .dispatch_tree + .set_active_node(deferred_draw.parent_node); + + let prepaint_start = self.prepaint_index(); + if let Some(element) = deferred_draw.element.as_mut() { + self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| { + element.prepaint(cx) + }); + } else { + self.reuse_prepaint(deferred_draw.prepaint_range.clone()); + } + let prepaint_end = self.prepaint_index(); + deferred_draw.prepaint_range = prepaint_start..prepaint_end; + } + assert_eq!( + self.window.next_frame.deferred_draws.len(), + 0, + "cannot call defer_draw during deferred drawing" + ); + self.window.next_frame.deferred_draws = deferred_draws; + self.window.element_id_stack.clear(); + self.window.text_style_stack.clear(); + } + + fn paint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { + assert_eq!(self.window.element_id_stack.len(), 0); + + let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); + for deferred_draw_ix in deferred_draw_indices { + let mut deferred_draw = &mut deferred_draws[*deferred_draw_ix]; + self.window.element_id_stack = deferred_draw.element_id_stack.clone(); + self.window + .next_frame + .dispatch_tree + .set_active_node(deferred_draw.parent_node); + + let paint_start = self.paint_index(); + if let Some(element) = deferred_draw.element.as_mut() { + element.paint(self); + } else { + self.reuse_paint(deferred_draw.paint_range.clone()); + } + let paint_end = self.paint_index(); + deferred_draw.paint_range = paint_start..paint_end; + } + self.window.next_frame.deferred_draws = deferred_draws; + self.window.element_id_stack.clear(); + } + + pub(crate) fn prepaint_index(&self) -> PrepaintStateIndex { + PrepaintStateIndex { + hitboxes_index: self.window.next_frame.hitboxes.len(), + tooltips_index: self.window.next_frame.tooltip_requests.len(), + deferred_draws_index: self.window.next_frame.deferred_draws.len(), + dispatch_tree_index: self.window.next_frame.dispatch_tree.len(), + accessed_element_states_index: self.window.next_frame.accessed_element_states.len(), + line_layout_index: self.window.text_system.layout_index(), + } + } + + pub(crate) fn reuse_prepaint(&mut self, range: Range) { + let window = &mut self.window; + window.next_frame.hitboxes.extend( + window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index] + .iter() + .cloned(), + ); + window.next_frame.tooltip_requests.extend( + window.rendered_frame.tooltip_requests + [range.start.tooltips_index..range.end.tooltips_index] + .iter_mut() + .map(|request| request.take()), + ); + window.next_frame.accessed_element_states.extend( + window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index + ..range.end.accessed_element_states_index] + .iter() + .cloned(), + ); + window + .text_system + .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index); + + let reused_subtree = window.next_frame.dispatch_tree.reuse_subtree( + range.start.dispatch_tree_index..range.end.dispatch_tree_index, + &mut window.rendered_frame.dispatch_tree, + ); + window.next_frame.deferred_draws.extend( + window.rendered_frame.deferred_draws + [range.start.deferred_draws_index..range.end.deferred_draws_index] + .iter() + .map(|deferred_draw| DeferredDraw { + parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node), + element_id_stack: deferred_draw.element_id_stack.clone(), + text_style_stack: deferred_draw.text_style_stack.clone(), + priority: deferred_draw.priority, + element: None, + absolute_offset: deferred_draw.absolute_offset, + prepaint_range: deferred_draw.prepaint_range.clone(), + paint_range: deferred_draw.paint_range.clone(), + }), + ); + } + + pub(crate) fn paint_index(&self) -> PaintIndex { + PaintIndex { + scene_index: self.window.next_frame.scene.len(), + mouse_listeners_index: self.window.next_frame.mouse_listeners.len(), + input_handlers_index: self.window.next_frame.input_handlers.len(), + cursor_styles_index: self.window.next_frame.cursor_styles.len(), + accessed_element_states_index: self.window.next_frame.accessed_element_states.len(), + line_layout_index: self.window.text_system.layout_index(), + } + } + + pub(crate) fn reuse_paint(&mut self, range: Range) { + let window = &mut self.window; + + window.next_frame.cursor_styles.extend( + window.rendered_frame.cursor_styles + [range.start.cursor_styles_index..range.end.cursor_styles_index] + .iter() + .cloned(), + ); + window.next_frame.input_handlers.extend( + window.rendered_frame.input_handlers + [range.start.input_handlers_index..range.end.input_handlers_index] + .iter_mut() + .map(|handler| handler.take()), + ); + window.next_frame.mouse_listeners.extend( + window.rendered_frame.mouse_listeners + [range.start.mouse_listeners_index..range.end.mouse_listeners_index] + .iter_mut() + .map(|listener| listener.take()), + ); + window.next_frame.accessed_element_states.extend( + window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index + ..range.end.accessed_element_states_index] + .iter() + .cloned(), + ); + window + .text_system + .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index); + window.next_frame.scene.replay( + range.start.scene_index..range.end.scene_index, + &window.rendered_frame.scene, + ); + } + + /// Push a text style onto the stack, and call a function with that style active. + /// Use [`AppContext::text_style`] to get the current, combined text style. This method + /// should only be called as part of element drawing. + pub fn with_text_style(&mut self, style: Option, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during request_layout, prepaint, or paint" + ); + if let Some(style) = style { + self.window.text_style_stack.push(style); + let result = f(self); + self.window.text_style_stack.pop(); + result + } else { + f(self) + } + } + + /// Updates the cursor style at the platform level. This method should only be called + /// during the prepaint phase of element drawing. + pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + self.window + .next_frame + .cursor_styles + .push(CursorStyleRequest { + hitbox_id: hitbox.id, + style, + }); + } + + /// Sets a tooltip to be rendered for the upcoming frame. This method should only be called + /// during the paint phase of element drawing. + pub fn set_tooltip(&mut self, tooltip: AnyTooltip) -> TooltipId { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + let id = TooltipId(post_inc(&mut self.window.next_tooltip_id.0)); + self.window + .next_frame + .tooltip_requests + .push(Some(TooltipRequest { id, tooltip })); + id + } + + /// 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. This method should + /// only be called as part of element drawing. + pub fn with_element_id( + &mut self, + id: Option>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during request_layout, prepaint, or paint" + ); + 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. This method should only be called during element drawing. + pub fn with_content_mask( + &mut self, + mask: Option>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during request_layout, prepaint, or paint" + ); + if let Some(mask) = mask { + let mask = mask.intersect(&self.content_mask()); + self.window_mut().content_mask_stack.push(mask); + let result = f(self); + self.window_mut().content_mask_stack.pop(); + result + } else { + f(self) + } + } + + /// Updates the global element offset relative to the current offset. This is used to implement + /// scrolling. This method should only be called during the prepaint phase of element drawing. + pub fn with_element_offset( + &mut self, + offset: Point, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, or prepaint" + ); + + 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. This method should only be called during + /// the prepaint phase of element drawing. + pub fn with_absolute_element_offset( + &mut self, + offset: Point, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, or prepaint" + ); + self.window_mut().element_offset_stack.push(offset); + let result = f(self); + self.window_mut().element_offset_stack.pop(); + result + } + + /// Perform prepaint on child elements in a "retryable" manner, so that any side effects + /// of prepaints can be discarded before prepainting again. This is used to support autoscroll + /// where we need to prepaint children to detect the autoscroll bounds, then adjust the + /// element offset and prepaint again. See [`List`] for an example. This method should only be + /// called during the prepaint phase of element drawing. + pub fn transact(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + let index = self.prepaint_index(); + let result = f(self); + if result.is_err() { + self.window + .next_frame + .hitboxes + .truncate(index.hitboxes_index); + self.window + .next_frame + .tooltip_requests + .truncate(index.tooltips_index); + self.window + .next_frame + .deferred_draws + .truncate(index.deferred_draws_index); + self.window + .next_frame + .dispatch_tree + .truncate(index.dispatch_tree_index); + self.window + .next_frame + .accessed_element_states + .truncate(index.accessed_element_states_index); + self.window + .text_system + .truncate_layouts(index.line_layout_index); + } + result + } + + /// When you call this method during [`prepaint`], containing elements will attempt to + /// scroll to cause the specified bounds to become visible. When they decide to autoscroll, they will call + /// [`prepaint`] again with a new set of bounds. See [`List`] for an example of an element + /// that supports this method being called on the elements it contains. This method should only be + /// called during the prepaint phase of element drawing. + pub fn request_autoscroll(&mut self, bounds: Bounds) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + self.window.requested_autoscroll = Some(bounds); + } + + /// This method can be called from a containing element such as [`List`] to support the autoscroll behavior + /// described in [`request_autoscroll`]. + pub fn take_autoscroll(&mut self) -> Option> { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + self.window.requested_autoscroll.take() + } + + /// Remove an asset from GPUI's cache + pub fn remove_cached_asset( + &mut self, + source: &A::Source, + ) -> Option { + self.asset_cache.remove::(source) + } + + /// Asynchronously load an asset, if the asset hasn't finished loading this will return None. + /// Your view will be re-drawn once the asset has finished loading. + /// + /// Note that the multiple calls to this method will only result in one `Asset::load` call. + /// The results of that call will be cached, and returned on subsequent uses of this API. + /// + /// Use [Self::remove_cached_asset] to reload your asset. + pub fn use_cached_asset( + &mut self, + source: &A::Source, + ) -> Option { + self.asset_cache.get::(source).or_else(|| { + if let Some(asset) = self.use_asset::(source) { + self.asset_cache + .insert::(source.to_owned(), asset.clone()); + Some(asset) + } else { + None + } + }) + } + + /// Asynchronously load an asset, if the asset hasn't finished loading this will return None. + /// Your view will be re-drawn once the asset has finished loading. + /// + /// Note that the multiple calls to this method will only result in one `Asset::load` call at a + /// time. + /// + /// This asset will not be cached by default, see [Self::use_cached_asset] + pub fn use_asset(&mut self, source: &A::Source) -> Option { + let asset_id = (TypeId::of::(), hash(source)); + let mut is_first = false; + let task = self + .loading_assets + .remove(&asset_id) + .map(|boxed_task| *boxed_task.downcast::>>().unwrap()) + .unwrap_or_else(|| { + is_first = true; + let future = A::load(source.clone(), self); + let task = self.background_executor().spawn(future).shared(); + task + }); + + task.clone().now_or_never().or_else(|| { + if is_first { + let parent_id = self.parent_view_id(); + self.spawn({ + let task = task.clone(); + |mut cx| async move { + task.await; + + cx.on_next_frame(move |cx| { + if let Some(parent_id) = parent_id { + cx.notify(parent_id) + } else { + cx.refresh() + } + }); + } + }) + .detach(); + } + + self.loading_assets.insert(asset_id, Box::new(task)); + + None + }) + } + + /// Obtain the current element offset. This method should only be called during the + /// prepaint phase of element drawing. + pub fn element_offset(&self) -> Point { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + self.window() + .element_offset_stack + .last() + .copied() + .unwrap_or_default() + } + + /// Obtain the current content mask. This method should only be called during element drawing. + pub fn content_mask(&self) -> ContentMask { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during prepaint, or paint" + ); + self.window() + .content_mask_stack + .last() + .cloned() + .unwrap_or_else(|| ContentMask { + bounds: Bounds { + origin: Point::default(), + size: self.window().viewport_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. This method should only be called as part of element drawing. + pub fn with_element_state( + &mut self, + element_id: Option, + f: impl FnOnce(Option>, &mut Self) -> (R, Option), + ) -> R + where + S: 'static, + { + debug_assert!( + matches!( + self.window.draw_phase, + DrawPhase::Prepaint | DrawPhase::Paint + ), + "this method can only be called during request_layout, prepaint, or paint" + ); + let id_is_none = element_id.is_none(); + self.with_element_id(element_id, |cx| { + if id_is_none { + let (result, state) = f(None, cx); + debug_assert!(state.is_none(), "you must not return an element state when passing None for the element id"); + result + } else { + let global_id = cx.window().element_id_stack.clone(); + let key = (global_id, TypeId::of::()); + cx.window.next_frame.accessed_element_states.push(key.clone()); + + if let Some(any) = cx + .window_mut() + .next_frame + .element_states + .remove(&key) + .or_else(|| { + cx.window_mut() + .rendered_frame + .element_states + .remove(&key) + }) + { + let ElementStateBox { + inner, + #[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("reentrant call to with_element_state for the same state type and element id"); + let (result, state) = f(Some(Some(state)), cx); + state_box.replace(state.expect("you must return ")); + cx.window_mut() + .next_frame + .element_states + .insert(key, ElementStateBox { + inner: state_box, + #[cfg(debug_assertions)] + type_name + }); + result + } else { + let (result, state) = f(Some(None), cx); + cx.window_mut() + .next_frame + .element_states + .insert(key, + ElementStateBox { + inner: Box::new(Some(state.expect("you must return Some when you pass some element id"))), + #[cfg(debug_assertions)] + type_name: std::any::type_name::() + } + + ); + result + } + } + }) + } + + /// Defers the drawing of the given element, scheduling it to be painted on top of the currently-drawn tree + /// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements, + /// with higher values being drawn on top. + /// + /// This method should only be called as part of the prepaint phase of element drawing. + pub fn defer_draw( + &mut self, + element: AnyElement, + absolute_offset: Point, + priority: usize, + ) { + let window = &mut self.window; + debug_assert_eq!( + window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout or prepaint" + ); + let parent_node = window.next_frame.dispatch_tree.active_node_id().unwrap(); + window.next_frame.deferred_draws.push(DeferredDraw { + parent_node, + element_id_stack: window.element_id_stack.clone(), + text_style_stack: window.text_style_stack.clone(), + priority, + element: Some(element), + absolute_offset, + prepaint_range: PrepaintStateIndex::default()..PrepaintStateIndex::default(), + paint_range: PaintIndex::default()..PaintIndex::default(), + }); + } + + /// Creates a new painting layer for the specified bounds. A "layer" is a batch + /// of geometry that are non-overlapping and have the same draw order. This is typically used + /// for performance reasons. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_layer(&mut self, bounds: Bounds, f: impl FnOnce(&mut Self) -> R) -> R { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + let clipped_bounds = bounds.intersect(&content_mask.bounds); + if !clipped_bounds.is_empty() { + self.window + .next_frame + .scene + .push_layer(clipped_bounds.scale(scale_factor)); + } + + let result = f(self); + + if !clipped_bounds.is_empty() { + self.window.next_frame.scene.pop_layer(); + } + + result + } + + /// Paint one or more drop shadows into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_shadows( + &mut self, + bounds: Bounds, + corner_radii: Corners, + shadows: &[BoxShadow], + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + for shadow in shadows { + let mut shadow_bounds = bounds; + shadow_bounds.origin += shadow.offset; + shadow_bounds.dilate(shadow.spread_radius); + self.window.next_frame.scene.insert_primitive(Shadow { + order: 0, + blur_radius: shadow.blur_radius.scale(scale_factor), + bounds: shadow_bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + corner_radii: corner_radii.scale(scale_factor), + color: shadow.color, + }); + } + } + + /// 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`](crate::fill), [`outline`](crate::outline), and [`quad`](crate::quad) to construct this type. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_quad(&mut self, quad: PaintQuad) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + self.window.next_frame.scene.insert_primitive(Quad { + order: 0, + pad: 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. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_path(&mut self, mut path: Path, color: impl Into) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let content_mask = self.content_mask(); + path.content_mask = content_mask; + path.color = color.into(); + self.window + .next_frame + .scene + .insert_primitive(path.scale(scale_factor)); + } + + /// Paint an underline into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_underline( + &mut self, + origin: Point, + width: Pixels, + style: &UnderlineStyle, + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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(); + + self.window.next_frame.scene.insert_primitive(Underline { + order: 0, + pad: 0, + bounds: bounds.scale(scale_factor), + content_mask: content_mask.scale(scale_factor), + color: style.color.unwrap_or_default(), + thickness: style.thickness.scale(scale_factor), + wavy: style.wavy, + }); + } + + /// Paint a strikethrough into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_strikethrough( + &mut self, + origin: Point, + width: Pixels, + style: &StrikethroughStyle, + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let height = style.thickness; + let bounds = Bounds { + origin, + size: size(width, height), + }; + let content_mask = self.content_mask(); + + self.window.next_frame.scene.insert_primitive(Underline { + order: 0, + pad: 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: false, + }); + } + + /// Paints 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. + /// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or + /// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem). + /// This method is only useful if you need to paint a single glyph that has already been shaped. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_glyph( + &mut self, + origin: Point, + font_id: FontId, + glyph_id: GlyphId, + font_size: Pixels, + color: Hsla, + ) -> Result<()> { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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); + self.window + .next_frame + .scene + .insert_primitive(MonochromeSprite { + order: 0, + pad: 0, + bounds, + content_mask, + color, + tile, + transformation: TransformationMatrix::unit(), + }); + } + Ok(()) + } + + /// Paints 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. + /// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or + /// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem). + /// This method is only useful if you need to paint a single emoji that has already been shaped. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_emoji( + &mut self, + origin: Point, + font_id: FontId, + glyph_id: GlyphId, + font_size: Pixels, + ) -> Result<()> { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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); + + self.window + .next_frame + .scene + .insert_primitive(PolychromeSprite { + order: 0, + grayscale: false, + bounds, + corner_radii: Default::default(), + content_mask, + tile, + }); + } + Ok(()) + } + + /// Paint a monochrome SVG into the scene for the next frame at the current stacking context. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_svg( + &mut self, + bounds: Bounds, + path: SharedString, + transformation: TransformationMatrix, + color: Hsla, + ) -> Result<()> { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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); + + self.window + .next_frame + .scene + .insert_primitive(MonochromeSprite { + order: 0, + pad: 0, + bounds, + content_mask, + color, + tile, + transformation, + }); + + Ok(()) + } + + /// Paint an image into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn paint_image( + &mut self, + bounds: Bounds, + corner_radii: Corners, + data: Arc, + grayscale: bool, + ) -> Result<()> { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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); + + self.window + .next_frame + .scene + .insert_primitive(PolychromeSprite { + order: 0, + grayscale, + bounds, + content_mask, + corner_radii, + tile, + }); + Ok(()) + } + + /// Paint a surface into the scene for the next frame at the current z-index. + /// + /// This method should only be called as part of the paint phase of element drawing. + #[cfg(target_os = "macos")] + pub fn paint_surface(&mut self, bounds: Bounds, image_buffer: CVImageBuffer) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + let scale_factor = self.scale_factor(); + let bounds = bounds.scale(scale_factor); + let content_mask = self.content_mask().scale(scale_factor); + self.window + .next_frame + .scene + .insert_primitive(crate::Surface { + 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::request_layout`] trait method and enables any element to participate in layout. + /// + /// This method should only be called as part of the request_layout or prepaint phase of element drawing. + pub fn request_layout( + &mut self, + style: &Style, + children: impl IntoIterator, + ) -> LayoutId { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, or prepaint" + ); + + 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`. + /// + /// This method should only be called as part of the request_layout or prepaint phase of element drawing. + pub fn request_measured_layout< + F: FnMut(Size>, Size, &mut WindowContext) -> Size + + 'static, + >( + &mut self, + style: Style, + measure: F, + ) -> LayoutId { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, or prepaint" + ); + + 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. + /// + /// This method should only be called as part of the prepaint phase of element drawing. + pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, or prepaint" + ); + + 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 will usually be invoked by + /// GPUI itself automatically in order to pass your element its `Bounds` automatically. + /// + /// This method should only be called as part of element drawing. + pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Bounds { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during request_layout, prepaint, or paint" + ); + + let mut bounds = self + .window + .layout_engine + .as_mut() + .unwrap() + .layout_bounds(layout_id) + .map(Into::into); + bounds.origin += self.element_offset(); + bounds + } + + /// This method should be called during `prepaint`. You can use + /// the returned [Hitbox] during `paint` or in an event handler + /// to determine whether the inserted hitbox was the topmost. + /// + /// This method should only be called as part of the prepaint phase of element drawing. + pub fn insert_hitbox(&mut self, bounds: Bounds, opaque: bool) -> Hitbox { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + + let content_mask = self.content_mask(); + let window = &mut self.window; + let id = window.next_hitbox_id; + window.next_hitbox_id.0 += 1; + let hitbox = Hitbox { + id, + bounds, + content_mask, + opaque, + }; + window.next_frame.hitboxes.push(hitbox.clone()); + hitbox + } + + /// Sets the key context for the current element. This context will be used to translate + /// keybindings into actions. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn set_key_context(&mut self, context: KeyContext) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + self.window + .next_frame + .dispatch_tree + .set_key_context(context); + } + + /// Sets the focus handle for the current element. This handle will be used to manage focus state + /// and keyboard event dispatch for the element. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn set_focus_handle(&mut self, focus_handle: &FocusHandle) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + self.window + .next_frame + .dispatch_tree + .set_focus_id(focus_handle.id); + } + + /// Sets the view id for the current element, which will be used to manage view caching. + /// + /// This method should only be called as part of element prepaint. We plan on removing this + /// method eventually when we solve some issues that require us to construct editor elements + /// directly instead of always using editors via views. + pub fn set_view_id(&mut self, view_id: EntityId) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Prepaint, + "this method can only be called during prepaint" + ); + self.window.next_frame.dispatch_tree.set_view_id(view_id); + } + + /// Get the last view id for the current element + pub fn parent_view_id(&mut self) -> Option { + self.window.next_frame.dispatch_tree.parent_view_id() + } + + /// 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. + /// + /// This method should only be called as part of the paint phase of element drawing. + /// + /// [element_input_handler]: crate::ElementInputHandler + pub fn handle_input(&mut self, focus_handle: &FocusHandle, input_handler: impl InputHandler) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + if focus_handle.is_focused(self) { + let cx = self.to_async(); + self.window + .next_frame + .input_handlers + .push(Some(PlatformInputHandler::new(cx, 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. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn on_mouse_event( + &mut self, + mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + self.window.next_frame.mouse_listeners.push(Some(Box::new( + move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { + if let Some(event) = event.downcast_ref() { + handler(event, 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. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn on_key_event( + &mut self, + listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + 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 a modifiers changed event listener on the window for the next frame. + /// + /// 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. + /// + /// This method should only be called as part of the paint phase of element drawing. + pub fn on_modifiers_changed( + &mut self, + listener: impl Fn(&ModifiersChangedEvent, &mut WindowContext) + 'static, + ) { + debug_assert_eq!( + self.window.draw_phase, + DrawPhase::Paint, + "this method can only be called during paint" + ); + + self.window + .next_frame + .dispatch_tree + .on_modifiers_changed(Rc::new( + move |event: &ModifiersChangedEvent, cx: &mut WindowContext<'_>| { + listener(event, cx) + }, + )); + } + fn reset_cursor_style(&self) { // Set the cursor only if we're the active window. if self.is_window_active() { @@ -1282,28 +2902,28 @@ impl<'a> WindowContext<'a> { } let mut mouse_listeners = mem::take(&mut self.window.rendered_frame.mouse_listeners); - self.with_element_context(|cx| { - // 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 listener in &mut mouse_listeners { + + // 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 listener in &mut mouse_listeners { + let listener = listener.as_mut().unwrap(); + listener(event, DispatchPhase::Capture, self); + if !self.app.propagate_event { + break; + } + } + + // Bubble phase, where most normal handlers do their work. + if self.app.propagate_event { + for listener in mouse_listeners.iter_mut().rev() { let listener = listener.as_mut().unwrap(); - listener(event, DispatchPhase::Capture, cx); - if !cx.app.propagate_event { + listener(event, DispatchPhase::Bubble, self); + if !self.app.propagate_event { break; } } + } - // Bubble phase, where most normal handlers do their work. - if cx.app.propagate_event { - for listener in mouse_listeners.iter_mut().rev() { - let listener = listener.as_mut().unwrap(); - listener(event, DispatchPhase::Bubble, cx); - if !cx.app.propagate_event { - break; - } - } - } - }); self.window.rendered_frame.mouse_listeners = mouse_listeners; if self.has_active_drag() { @@ -1425,9 +3045,7 @@ impl<'a> WindowContext<'a> { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { - self.with_element_context(|cx| { - key_listener(event, DispatchPhase::Capture, cx); - }); + key_listener(event, DispatchPhase::Capture, self); if !self.propagate_event { return; } @@ -1439,9 +3057,7 @@ 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() { - self.with_element_context(|cx| { - key_listener(event, DispatchPhase::Bubble, cx); - }); + key_listener(event, DispatchPhase::Bubble, self); if !self.propagate_event { return; } @@ -1460,9 +3076,7 @@ impl<'a> WindowContext<'a> { for node_id in dispatch_path.iter().rev() { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for listener in node.modifiers_changed_listeners.clone() { - self.with_element_context(|cx| { - listener(event, cx); - }); + listener(event, self); if !self.propagate_event { return; } @@ -1574,9 +3188,7 @@ impl<'a> WindowContext<'a> { { let any_action = action.as_any(); if action_type == any_action.type_id() { - self.with_element_context(|cx| { - listener(any_action, DispatchPhase::Capture, cx); - }); + listener(any_action, DispatchPhase::Capture, self); if !self.propagate_event { return; @@ -1596,10 +3208,7 @@ 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 - - self.with_element_context(|cx| { - listener(any_action, DispatchPhase::Bubble, cx); - }); + listener(any_action, DispatchPhase::Bubble, self); if !self.propagate_event { return; @@ -2895,7 +4504,7 @@ impl From<(&'static str, u64)> for ElementId { } /// A rectangle to be rendered in the window at the given position and size. -/// Passed as an argument [`ElementContext::paint_quad`]. +/// Passed as an argument [`WindowContext::paint_quad`]. #[derive(Clone)] pub struct PaintQuad { /// The bounds of the quad within the window. diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs deleted file mode 100644 index 22dc083a19..0000000000 --- a/crates/gpui/src/window/element_cx.rs +++ /dev/null @@ -1,1554 +0,0 @@ -//! The element context is the main interface for interacting with the frame during a paint. -//! -//! Elements are hierarchical and with a few exceptions the context accumulates state in a stack -//! as it processes all of the elements in the frame. The methods that interact with this stack -//! are generally marked with `with_*`, and take a callback to denote the region of code that -//! should be executed with that state. -//! -//! The other main interface is the `paint_*` family of methods, which push basic drawing commands -//! to the GPU. Everything in a GPUI app is drawn with these methods. -//! -//! There are also several internal methods that GPUI uses, such as [`ElementContext::with_element_state`] -//! to call the paint and layout methods on elements. These have been included as they're often useful -//! for taking manual control of the layouting or painting of specialized elements. - -use std::{ - any::{Any, TypeId}, - borrow::{Borrow, BorrowMut, Cow}, - cmp, mem, - ops::Range, - rc::Rc, - sync::Arc, -}; - -use anyhow::Result; -use collections::FxHashMap; -use derive_more::{Deref, DerefMut}; -use futures::{future::Shared, FutureExt}; -#[cfg(target_os = "macos")] -use media::core_video::CVImageBuffer; -use smallvec::SmallVec; -use util::post_inc; - -use crate::{ - hash, point, prelude::*, px, size, AnyElement, AnyTooltip, AppContext, Asset, AvailableSpace, - Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId, - DispatchPhase, DispatchTree, DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle, - FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext, - KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite, MouseEvent, - PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, - RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, - StrikethroughStyle, Style, Task, TextStyleRefinement, TransformationMatrix, Underline, - UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS, -}; - -pub(crate) type AnyMouseListener = - Box; - -#[derive(Clone)] -pub(crate) struct CursorStyleRequest { - pub(crate) hitbox_id: HitboxId, - pub(crate) style: CursorStyle, -} - -/// An identifier for a [Hitbox]. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct HitboxId(usize); - -impl HitboxId { - /// Checks if the hitbox with this id is currently hovered. - pub fn is_hovered(&self, cx: &WindowContext) -> bool { - cx.window.mouse_hit_test.0.contains(self) - } -} - -/// A rectangular region that potentially blocks hitboxes inserted prior. -/// See [ElementContext::insert_hitbox] for more details. -#[derive(Clone, Debug, Deref)] -pub struct Hitbox { - /// A unique identifier for the hitbox. - pub id: HitboxId, - /// The bounds of the hitbox. - #[deref] - pub bounds: Bounds, - /// The content mask when the hitbox was inserted. - pub content_mask: ContentMask, - /// Whether the hitbox occludes other hitboxes inserted prior. - pub opaque: bool, -} - -impl Hitbox { - /// Checks if the hitbox is currently hovered. - pub fn is_hovered(&self, cx: &WindowContext) -> bool { - self.id.is_hovered(cx) - } -} - -#[derive(Default, Eq, PartialEq)] -pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>); - -/// An identifier for a tooltip. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub struct TooltipId(usize); - -impl TooltipId { - /// Checks if the tooltip is currently hovered. - pub fn is_hovered(&self, cx: &WindowContext) -> bool { - cx.window - .tooltip_bounds - .as_ref() - .map_or(false, |tooltip_bounds| { - tooltip_bounds.id == *self && tooltip_bounds.bounds.contains(&cx.mouse_position()) - }) - } -} - -pub(crate) struct TooltipBounds { - id: TooltipId, - bounds: Bounds, -} - -#[derive(Clone)] -pub(crate) struct TooltipRequest { - id: TooltipId, - tooltip: AnyTooltip, -} - -pub(crate) struct DeferredDraw { - priority: usize, - parent_node: DispatchNodeId, - element_id_stack: GlobalElementId, - text_style_stack: Vec, - element: Option, - absolute_offset: Point, - prepaint_range: Range, - paint_range: Range, -} - -pub(crate) struct Frame { - pub(crate) focus: Option, - pub(crate) window_active: bool, - pub(crate) element_states: FxHashMap<(GlobalElementId, TypeId), ElementStateBox>, - accessed_element_states: Vec<(GlobalElementId, TypeId)>, - pub(crate) mouse_listeners: Vec>, - pub(crate) dispatch_tree: DispatchTree, - pub(crate) scene: Scene, - pub(crate) hitboxes: Vec, - pub(crate) deferred_draws: Vec, - pub(crate) input_handlers: Vec>, - pub(crate) tooltip_requests: Vec>, - pub(crate) cursor_styles: Vec, - #[cfg(any(test, feature = "test-support"))] - pub(crate) debug_bounds: FxHashMap>, -} - -#[derive(Clone, Default)] -pub(crate) struct PrepaintStateIndex { - hitboxes_index: usize, - tooltips_index: usize, - deferred_draws_index: usize, - dispatch_tree_index: usize, - accessed_element_states_index: usize, - line_layout_index: LineLayoutIndex, -} - -#[derive(Clone, Default)] -pub(crate) struct PaintIndex { - scene_index: usize, - mouse_listeners_index: usize, - input_handlers_index: usize, - cursor_styles_index: usize, - accessed_element_states_index: usize, - line_layout_index: LineLayoutIndex, -} - -impl Frame { - pub(crate) fn new(dispatch_tree: DispatchTree) -> Self { - Frame { - focus: None, - window_active: false, - element_states: FxHashMap::default(), - accessed_element_states: Vec::new(), - mouse_listeners: Vec::new(), - dispatch_tree, - scene: Scene::default(), - hitboxes: Vec::new(), - deferred_draws: Vec::new(), - input_handlers: Vec::new(), - tooltip_requests: Vec::new(), - cursor_styles: Vec::new(), - - #[cfg(any(test, feature = "test-support"))] - debug_bounds: FxHashMap::default(), - } - } - - pub(crate) fn clear(&mut self) { - self.element_states.clear(); - self.accessed_element_states.clear(); - self.mouse_listeners.clear(); - self.dispatch_tree.clear(); - self.scene.clear(); - self.input_handlers.clear(); - self.tooltip_requests.clear(); - self.cursor_styles.clear(); - self.hitboxes.clear(); - self.deferred_draws.clear(); - } - - pub(crate) fn hit_test(&self, position: Point) -> HitTest { - let mut hit_test = HitTest::default(); - for hitbox in self.hitboxes.iter().rev() { - let bounds = hitbox.bounds.intersect(&hitbox.content_mask.bounds); - if bounds.contains(&position) { - hit_test.0.push(hitbox.id); - if hitbox.opaque { - break; - } - } - } - hit_test - } - - 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) { - for element_state_key in &self.accessed_element_states { - if let Some(element_state) = prev_frame.element_states.remove(element_state_key) { - self.element_states - .insert(element_state_key.clone(), element_state); - } - } - - 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> { - /// Convert this window context into an ElementContext in this callback. - /// If you need to use this method, you're probably intermixing the imperative - /// and declarative APIs, which is not recommended. - 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 reserve_model(&mut self) -> Self::Result> { - self.cx.reserve_model() - } - - fn insert_model( - &mut self, - reservation: crate::Reservation, - build_model: impl FnOnce(&mut crate::ModelContext<'_, T>) -> T, - ) -> Self::Result> { - self.cx.insert_model(reservation, 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 draw_roots(&mut self) { - self.window.draw_phase = DrawPhase::Layout; - self.window.tooltip_bounds.take(); - - // Layout all root elements. - let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any(); - root_element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self); - - let mut sorted_deferred_draws = - (0..self.window.next_frame.deferred_draws.len()).collect::>(); - sorted_deferred_draws.sort_by_key(|ix| self.window.next_frame.deferred_draws[*ix].priority); - self.prepaint_deferred_draws(&sorted_deferred_draws); - - let mut prompt_element = None; - let mut active_drag_element = None; - let mut tooltip_element = None; - if let Some(prompt) = self.window.prompt.take() { - let mut element = prompt.view.any_view().into_any(); - element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self); - prompt_element = Some(element); - self.window.prompt = Some(prompt); - } else if let Some(active_drag) = self.app.active_drag.take() { - let mut element = active_drag.view.clone().into_any(); - let offset = self.mouse_position() - active_drag.cursor_offset; - element.prepaint_as_root(offset, AvailableSpace::min_size(), self); - active_drag_element = Some(element); - self.app.active_drag = Some(active_drag); - } else { - tooltip_element = self.prepaint_tooltip(); - } - - self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position); - - // Now actually paint the elements. - self.window.draw_phase = DrawPhase::Paint; - root_element.paint(self); - - self.paint_deferred_draws(&sorted_deferred_draws); - - if let Some(mut prompt_element) = prompt_element { - prompt_element.paint(self) - } else if let Some(mut drag_element) = active_drag_element { - drag_element.paint(self); - } else if let Some(mut tooltip_element) = tooltip_element { - tooltip_element.paint(self); - } - } - - fn prepaint_tooltip(&mut self) -> Option { - let tooltip_request = self.window.next_frame.tooltip_requests.last().cloned()?; - let tooltip_request = tooltip_request.unwrap(); - let mut element = tooltip_request.tooltip.view.clone().into_any(); - let mouse_position = tooltip_request.tooltip.mouse_position; - let tooltip_size = element.layout_as_root(AvailableSpace::min_size(), self); - - let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size); - let window_bounds = Bounds { - origin: Point::default(), - size: self.viewport_size(), - }; - - if tooltip_bounds.right() > window_bounds.right() { - let new_x = mouse_position.x - tooltip_bounds.size.width - px(1.); - if new_x >= Pixels::ZERO { - tooltip_bounds.origin.x = new_x; - } else { - tooltip_bounds.origin.x = cmp::max( - Pixels::ZERO, - tooltip_bounds.origin.x - tooltip_bounds.right() - window_bounds.right(), - ); - } - } - - if tooltip_bounds.bottom() > window_bounds.bottom() { - let new_y = mouse_position.y - tooltip_bounds.size.height - px(1.); - if new_y >= Pixels::ZERO { - tooltip_bounds.origin.y = new_y; - } else { - tooltip_bounds.origin.y = cmp::max( - Pixels::ZERO, - tooltip_bounds.origin.y - tooltip_bounds.bottom() - window_bounds.bottom(), - ); - } - } - - self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx)); - - self.window.tooltip_bounds = Some(TooltipBounds { - id: tooltip_request.id, - bounds: tooltip_bounds, - }); - Some(element) - } - - fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { - assert_eq!(self.window.element_id_stack.len(), 0); - - let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); - for deferred_draw_ix in deferred_draw_indices { - let deferred_draw = &mut deferred_draws[*deferred_draw_ix]; - self.window.element_id_stack = deferred_draw.element_id_stack.clone(); - self.window.text_style_stack = deferred_draw.text_style_stack.clone(); - self.window - .next_frame - .dispatch_tree - .set_active_node(deferred_draw.parent_node); - - let prepaint_start = self.prepaint_index(); - if let Some(element) = deferred_draw.element.as_mut() { - self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| { - element.prepaint(cx) - }); - } else { - self.reuse_prepaint(deferred_draw.prepaint_range.clone()); - } - let prepaint_end = self.prepaint_index(); - deferred_draw.prepaint_range = prepaint_start..prepaint_end; - } - assert_eq!( - self.window.next_frame.deferred_draws.len(), - 0, - "cannot call defer_draw during deferred drawing" - ); - self.window.next_frame.deferred_draws = deferred_draws; - self.window.element_id_stack.clear(); - self.window.text_style_stack.clear(); - } - - fn paint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) { - assert_eq!(self.window.element_id_stack.len(), 0); - - let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws); - for deferred_draw_ix in deferred_draw_indices { - let mut deferred_draw = &mut deferred_draws[*deferred_draw_ix]; - self.window.element_id_stack = deferred_draw.element_id_stack.clone(); - self.window - .next_frame - .dispatch_tree - .set_active_node(deferred_draw.parent_node); - - let paint_start = self.paint_index(); - if let Some(element) = deferred_draw.element.as_mut() { - element.paint(self); - } else { - self.reuse_paint(deferred_draw.paint_range.clone()); - } - let paint_end = self.paint_index(); - deferred_draw.paint_range = paint_start..paint_end; - } - self.window.next_frame.deferred_draws = deferred_draws; - self.window.element_id_stack.clear(); - } - - pub(crate) fn prepaint_index(&self) -> PrepaintStateIndex { - PrepaintStateIndex { - hitboxes_index: self.window.next_frame.hitboxes.len(), - tooltips_index: self.window.next_frame.tooltip_requests.len(), - deferred_draws_index: self.window.next_frame.deferred_draws.len(), - dispatch_tree_index: self.window.next_frame.dispatch_tree.len(), - accessed_element_states_index: self.window.next_frame.accessed_element_states.len(), - line_layout_index: self.window.text_system.layout_index(), - } - } - - pub(crate) fn reuse_prepaint(&mut self, range: Range) { - let window = &mut self.window; - window.next_frame.hitboxes.extend( - window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index] - .iter() - .cloned(), - ); - window.next_frame.tooltip_requests.extend( - window.rendered_frame.tooltip_requests - [range.start.tooltips_index..range.end.tooltips_index] - .iter_mut() - .map(|request| request.take()), - ); - window.next_frame.accessed_element_states.extend( - window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index - ..range.end.accessed_element_states_index] - .iter() - .cloned(), - ); - window - .text_system - .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index); - - let reused_subtree = window.next_frame.dispatch_tree.reuse_subtree( - range.start.dispatch_tree_index..range.end.dispatch_tree_index, - &mut window.rendered_frame.dispatch_tree, - ); - window.next_frame.deferred_draws.extend( - window.rendered_frame.deferred_draws - [range.start.deferred_draws_index..range.end.deferred_draws_index] - .iter() - .map(|deferred_draw| DeferredDraw { - parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node), - element_id_stack: deferred_draw.element_id_stack.clone(), - text_style_stack: deferred_draw.text_style_stack.clone(), - priority: deferred_draw.priority, - element: None, - absolute_offset: deferred_draw.absolute_offset, - prepaint_range: deferred_draw.prepaint_range.clone(), - paint_range: deferred_draw.paint_range.clone(), - }), - ); - } - - pub(crate) fn paint_index(&self) -> PaintIndex { - PaintIndex { - scene_index: self.window.next_frame.scene.len(), - mouse_listeners_index: self.window.next_frame.mouse_listeners.len(), - input_handlers_index: self.window.next_frame.input_handlers.len(), - cursor_styles_index: self.window.next_frame.cursor_styles.len(), - accessed_element_states_index: self.window.next_frame.accessed_element_states.len(), - line_layout_index: self.window.text_system.layout_index(), - } - } - - pub(crate) fn reuse_paint(&mut self, range: Range) { - let window = &mut self.cx.window; - - window.next_frame.cursor_styles.extend( - window.rendered_frame.cursor_styles - [range.start.cursor_styles_index..range.end.cursor_styles_index] - .iter() - .cloned(), - ); - window.next_frame.input_handlers.extend( - window.rendered_frame.input_handlers - [range.start.input_handlers_index..range.end.input_handlers_index] - .iter_mut() - .map(|handler| handler.take()), - ); - window.next_frame.mouse_listeners.extend( - window.rendered_frame.mouse_listeners - [range.start.mouse_listeners_index..range.end.mouse_listeners_index] - .iter_mut() - .map(|listener| listener.take()), - ); - window.next_frame.accessed_element_states.extend( - window.rendered_frame.accessed_element_states[range.start.accessed_element_states_index - ..range.end.accessed_element_states_index] - .iter() - .cloned(), - ); - window - .text_system - .reuse_layouts(range.start.line_layout_index..range.end.line_layout_index); - window.next_frame.scene.replay( - range.start.scene_index..range.end.scene_index, - &window.rendered_frame.scene, - ); - } - - /// Push a text style onto the stack, and call a function with that style active. - /// Use [`AppContext::text_style`] to get the current, combined text 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.window.text_style_stack.push(style); - let result = f(self); - self.window.text_style_stack.pop(); - result - } else { - f(self) - } - } - - /// Updates the cursor style at the platform level. - pub fn set_cursor_style(&mut self, style: CursorStyle, hitbox: &Hitbox) { - self.window - .next_frame - .cursor_styles - .push(CursorStyleRequest { - hitbox_id: hitbox.id, - style, - }); - } - - /// Sets a tooltip to be rendered for the upcoming frame - pub fn set_tooltip(&mut self, tooltip: AnyTooltip) -> TooltipId { - let id = TooltipId(post_inc(&mut self.window.next_tooltip_id.0)); - self.window - .next_frame - .tooltip_requests - .push(Some(TooltipRequest { id, tooltip })); - id - } - - /// 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().content_mask_stack.push(mask); - let result = f(self); - self.window_mut().content_mask_stack.pop(); - result - } else { - f(self) - } - } - - /// 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().element_offset_stack.push(offset); - let result = f(self); - self.window_mut().element_offset_stack.pop(); - result - } - - /// Perform prepaint on child elements in a "retryable" manner, so that any side effects - /// of prepaints can be discarded before prepainting again. This is used to support autoscroll - /// where we need to prepaint children to detect the autoscroll bounds, then adjust the - /// element offset and prepaint again. See [`List`] for an example. - pub fn transact(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result { - let index = self.prepaint_index(); - let result = f(self); - if result.is_err() { - self.window - .next_frame - .hitboxes - .truncate(index.hitboxes_index); - self.window - .next_frame - .tooltip_requests - .truncate(index.tooltips_index); - self.window - .next_frame - .deferred_draws - .truncate(index.deferred_draws_index); - self.window - .next_frame - .dispatch_tree - .truncate(index.dispatch_tree_index); - self.window - .next_frame - .accessed_element_states - .truncate(index.accessed_element_states_index); - self.window - .text_system - .truncate_layouts(index.line_layout_index); - } - result - } - - /// When you call this method during [`prepaint`], containing elements will attempt to - /// scroll to cause the specified bounds to become visible. When they decide to autoscroll, they will call - /// [`prepaint`] again with a new set of bounds. See [`List`] for an example of an element - /// that supports this method being called on the elements it contains. - pub fn request_autoscroll(&mut self, bounds: Bounds) { - self.window.requested_autoscroll = Some(bounds); - } - - /// This method can be called from a containing element such as [`List`] to support the autoscroll behavior - /// described in [`request_autoscroll`]. - pub fn take_autoscroll(&mut self) -> Option> { - self.window.requested_autoscroll.take() - } - - /// Remove an asset from GPUI's cache - pub fn remove_cached_asset( - &mut self, - source: &A::Source, - ) -> Option { - self.asset_cache.remove::(source) - } - - /// Asynchronously load an asset, if the asset hasn't finished loading this will return None. - /// Your view will be re-drawn once the asset has finished loading. - /// - /// Note that the multiple calls to this method will only result in one `Asset::load` call. - /// The results of that call will be cached, and returned on subsequent uses of this API. - /// - /// Use [Self::remove_cached_asset] to reload your asset. - pub fn use_cached_asset( - &mut self, - source: &A::Source, - ) -> Option { - self.asset_cache.get::(source).or_else(|| { - if let Some(asset) = self.use_asset::(source) { - self.asset_cache - .insert::(source.to_owned(), asset.clone()); - Some(asset) - } else { - None - } - }) - } - - /// Asynchronously load an asset, if the asset hasn't finished loading this will return None. - /// Your view will be re-drawn once the asset has finished loading. - /// - /// Note that the multiple calls to this method will only result in one `Asset::load` call at a - /// time. - /// - /// This asset will not be cached by default, see [Self::use_cached_asset] - pub fn use_asset(&mut self, source: &A::Source) -> Option { - let asset_id = (TypeId::of::(), hash(source)); - let mut is_first = false; - let task = self - .loading_assets - .remove(&asset_id) - .map(|boxed_task| *boxed_task.downcast::>>().unwrap()) - .unwrap_or_else(|| { - is_first = true; - let future = A::load(source.clone(), self); - let task = self.background_executor().spawn(future).shared(); - task - }); - - task.clone().now_or_never().or_else(|| { - if is_first { - let parent_id = self.parent_view_id(); - self.spawn({ - let task = task.clone(); - |mut cx| async move { - task.await; - - cx.on_next_frame(move |cx| { - if let Some(parent_id) = parent_id { - cx.notify(parent_id) - } else { - cx.refresh() - } - }); - } - }) - .detach(); - } - - self.loading_assets.insert(asset_id, Box::new(task)); - - None - }) - } - - /// Obtain the current element offset. - pub fn element_offset(&self) -> Point { - self.window() - .element_offset_stack - .last() - .copied() - .unwrap_or_default() - } - - /// Obtain the current content mask. - pub fn content_mask(&self) -> ContentMask { - self.window() - .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, - element_id: Option, - f: impl FnOnce(Option>, &mut Self) -> (R, Option), - ) -> R - where - S: 'static, - { - let id_is_none = element_id.is_none(); - self.with_element_id(element_id, |cx| { - if id_is_none { - let (result, state) = f(None, cx); - debug_assert!(state.is_none(), "you must not return an element state when passing None for the element id"); - result - } else { - let global_id = cx.window().element_id_stack.clone(); - let key = (global_id, TypeId::of::()); - cx.window.next_frame.accessed_element_states.push(key.clone()); - - if let Some(any) = cx - .window_mut() - .next_frame - .element_states - .remove(&key) - .or_else(|| { - cx.window_mut() - .rendered_frame - .element_states - .remove(&key) - }) - { - let ElementStateBox { - inner, - #[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("reentrant call to with_element_state for the same state type and element id"); - let (result, state) = f(Some(Some(state)), cx); - state_box.replace(state.expect("you must return ")); - cx.window_mut() - .next_frame - .element_states - .insert(key, ElementStateBox { - inner: state_box, - #[cfg(debug_assertions)] - type_name - }); - result - } else { - let (result, state) = f(Some(None), cx); - cx.window_mut() - .next_frame - .element_states - .insert(key, - ElementStateBox { - inner: Box::new(Some(state.expect("you must return Some when you pass some element id"))), - #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } - - ); - result - } - } - }) - } - - /// Defers the drawing of the given element, scheduling it to be painted on top of the currently-drawn tree - /// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements, - /// with higher values being drawn on top. - pub fn defer_draw( - &mut self, - element: AnyElement, - absolute_offset: Point, - priority: usize, - ) { - let window = &mut self.cx.window; - assert_eq!( - window.draw_phase, - DrawPhase::Layout, - "defer_draw can only be called during request_layout or prepaint" - ); - let parent_node = window.next_frame.dispatch_tree.active_node_id().unwrap(); - window.next_frame.deferred_draws.push(DeferredDraw { - parent_node, - element_id_stack: window.element_id_stack.clone(), - text_style_stack: window.text_style_stack.clone(), - priority, - element: Some(element), - absolute_offset, - prepaint_range: PrepaintStateIndex::default()..PrepaintStateIndex::default(), - paint_range: PaintIndex::default()..PaintIndex::default(), - }); - } - - /// Creates a new painting layer for the specified bounds. A "layer" is a batch - /// of geometry that are non-overlapping and have the same draw order. This is typically used - /// for performance reasons. - pub fn paint_layer(&mut self, bounds: Bounds, f: impl FnOnce(&mut Self) -> R) -> R { - let scale_factor = self.scale_factor(); - let content_mask = self.content_mask(); - let clipped_bounds = bounds.intersect(&content_mask.bounds); - if !clipped_bounds.is_empty() { - self.window - .next_frame - .scene - .push_layer(clipped_bounds.scale(scale_factor)); - } - - let result = f(self); - - if !clipped_bounds.is_empty() { - self.window.next_frame.scene.pop_layer(); - } - - 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(); - for shadow in shadows { - let mut shadow_bounds = bounds; - shadow_bounds.origin += shadow.offset; - shadow_bounds.dilate(shadow.spread_radius); - self.window.next_frame.scene.insert_primitive(Shadow { - order: 0, - blur_radius: shadow.blur_radius.scale(scale_factor), - bounds: shadow_bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - corner_radii: corner_radii.scale(scale_factor), - color: shadow.color, - }); - } - } - - /// 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`](crate::fill), [`outline`](crate::outline), and [`quad`](crate::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(); - self.window.next_frame.scene.insert_primitive(Quad { - order: 0, - pad: 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(); - path.content_mask = content_mask; - path.color = color.into(); - self.window - .next_frame - .scene - .insert_primitive(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(); - - self.window.next_frame.scene.insert_primitive(Underline { - order: 0, - pad: 0, - bounds: bounds.scale(scale_factor), - content_mask: content_mask.scale(scale_factor), - color: style.color.unwrap_or_default(), - thickness: style.thickness.scale(scale_factor), - wavy: style.wavy, - }); - } - - /// Paint a strikethrough into the scene for the next frame at the current z-index. - pub fn paint_strikethrough( - &mut self, - origin: Point, - width: Pixels, - style: &StrikethroughStyle, - ) { - let scale_factor = self.scale_factor(); - let height = style.thickness; - let bounds = Bounds { - origin, - size: size(width, height), - }; - let content_mask = self.content_mask(); - - self.window.next_frame.scene.insert_primitive(Underline { - order: 0, - pad: 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: false, - }); - } - - /// Paints 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. - /// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or - /// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem). - /// This method is only useful if you need to paint a single glyph that has already been shaped. - 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); - self.window - .next_frame - .scene - .insert_primitive(MonochromeSprite { - order: 0, - pad: 0, - bounds, - content_mask, - color, - tile, - transformation: TransformationMatrix::unit(), - }); - } - Ok(()) - } - - /// Paints 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. - /// You should generally prefer to use the [`ShapedLine::paint`](crate::ShapedLine::paint) or - /// [`WrappedLine::paint`](crate::WrappedLine::paint) methods in the [`TextSystem`](crate::TextSystem). - /// This method is only useful if you need to paint a single emoji that has already been shaped. - 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); - - self.window - .next_frame - .scene - .insert_primitive(PolychromeSprite { - order: 0, - grayscale: false, - bounds, - corner_radii: Default::default(), - content_mask, - tile, - }); - } - 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, - transformation: TransformationMatrix, - 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); - - self.window - .next_frame - .scene - .insert_primitive(MonochromeSprite { - order: 0, - pad: 0, - bounds, - content_mask, - color, - tile, - transformation, - }); - - 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); - - self.window - .next_frame - .scene - .insert_primitive(PolychromeSprite { - order: 0, - grayscale, - bounds, - content_mask, - corner_radii, - tile, - }); - Ok(()) - } - - /// Paint a surface into the scene for the next frame at the current z-index. - #[cfg(target_os = "macos")] - 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); - self.window - .next_frame - .scene - .insert_primitive(crate::Surface { - 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 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 - } - - /// This method should be called during `prepaint`. You can use - /// the returned [Hitbox] during `paint` or in an event handler - /// to determine whether the inserted hitbox was the topmost. - pub fn insert_hitbox(&mut self, bounds: Bounds, opaque: bool) -> Hitbox { - let content_mask = self.content_mask(); - let window = &mut self.window; - let id = window.next_hitbox_id; - window.next_hitbox_id.0 += 1; - let hitbox = Hitbox { - id, - bounds, - content_mask, - opaque, - }; - window.next_frame.hitboxes.push(hitbox.clone()); - hitbox - } - - /// Sets the key context for the current element. This context will be used to translate - /// keybindings into actions. - pub fn set_key_context(&mut self, context: KeyContext) { - self.window - .next_frame - .dispatch_tree - .set_key_context(context); - } - - /// Sets the focus handle for the current element. This handle will be used to manage focus state - /// and keyboard event dispatch for the element. - pub fn set_focus_handle(&mut self, focus_handle: &FocusHandle) { - self.window - .next_frame - .dispatch_tree - .set_focus_id(focus_handle.id); - } - - /// Sets the view id for the current element, which will be used to manage view caching. - pub fn set_view_id(&mut self, view_id: EntityId) { - self.window.next_frame.dispatch_tree.set_view_id(view_id); - } - - /// Get the last view id for the current element - pub fn parent_view_id(&mut self) -> Option { - self.window.next_frame.dispatch_tree.parent_view_id() - } - - /// 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 cx = self.to_async(); - self.window - .next_frame - .input_handlers - .push(Some(PlatformInputHandler::new(cx, 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, - ) { - self.window.next_frame.mouse_listeners.push(Some(Box::new( - move |event: &dyn Any, phase: DispatchPhase, cx: &mut ElementContext<'_>| { - if let Some(event) = event.downcast_ref() { - handler(event, 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) - } - }, - )); - } - - /// Register a modifiers changed event listener on the window for the next frame. - /// - /// 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_modifiers_changed( - &mut self, - listener: impl Fn(&ModifiersChangedEvent, &mut ElementContext) + 'static, - ) { - self.window - .next_frame - .dispatch_tree - .on_modifiers_changed(Rc::new( - move |event: &ModifiersChangedEvent, cx: &mut ElementContext<'_>| { - listener(event, cx) - }, - )); - } -} diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 62ed376463..ec7cf222ba 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -155,7 +155,7 @@ impl FocusableView for ImageView { impl Render for ImageView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let checkered_background = |bounds: Bounds, _, cx: &mut ElementContext| { + let checkered_background = |bounds: Bounds, _, cx: &mut WindowContext| { let square_size = 32.0; let start_y = bounds.origin.y.0; diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 7547c9603f..909ab2a6f1 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1,11 +1,11 @@ use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine}; use gpui::{ - div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext, - FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, - InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext, - ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine, - StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle, - WeakView, WhiteSpace, WindowContext, WindowTextSystem, + div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, FocusHandle, Font, + FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, + Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, + MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, + Styled, TextRun, TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext, + WindowTextSystem, }; use itertools::Itertools; use language::CursorShape; @@ -85,7 +85,7 @@ impl LayoutCell { origin: Point, layout: &LayoutState, _visible_bounds: Bounds, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let pos = { let point = self.point; @@ -124,7 +124,7 @@ impl LayoutRect { } } - fn paint(&self, origin: Point, layout: &LayoutState, cx: &mut ElementContext) { + fn paint(&self, origin: Point, layout: &LayoutState, cx: &mut WindowContext) { let position = { let alac_point = self.point; point( @@ -418,7 +418,7 @@ impl TerminalElement { origin: Point, mode: TermMode, hitbox: &Hitbox, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); @@ -544,7 +544,7 @@ impl Element for TerminalElement { type RequestLayoutState = (); type PrepaintState = LayoutState; - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { self.interactivity.occlude_mouse(); let layout_id = self.interactivity.request_layout(cx, |mut style, cx| { style.size.width = relative(1.).into(); @@ -560,7 +560,7 @@ impl Element for TerminalElement { &mut self, bounds: Bounds, _: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Self::PrepaintState { self.interactivity .prepaint(bounds, bounds.size, cx, |_, _, hitbox, cx| { @@ -777,7 +777,7 @@ impl Element for TerminalElement { bounds: Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, - cx: &mut ElementContext<'_>, + cx: &mut WindowContext<'_>, ) { cx.paint_quad(fill(bounds, layout.background_color)); let origin = bounds.origin + Point::new(layout.gutter, px(0.)); diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index a3a9eba8f2..0d842d2a03 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds, - DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, InteractiveElement, - IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, - VisualContext, WindowContext, + DismissEvent, DispatchPhase, Element, ElementId, HitboxId, InteractiveElement, IntoElement, + LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, + WindowContext, }; use crate::prelude::*; @@ -112,8 +112,8 @@ impl PopoverMenu { fn with_element_state( &mut self, - cx: &mut ElementContext, - f: impl FnOnce(&mut Self, &mut PopoverMenuElementState, &mut ElementContext) -> R, + cx: &mut WindowContext, + f: impl FnOnce(&mut Self, &mut PopoverMenuElementState, &mut WindowContext) -> R, ) -> R { cx.with_element_state::, _>( Some(self.id.clone()), @@ -173,7 +173,7 @@ impl Element for PopoverMenu { fn request_layout( &mut self, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { self.with_element_state(cx, |this, element_state, cx| { let mut menu_layout_id = None; @@ -221,7 +221,7 @@ impl Element for PopoverMenu { &mut self, _bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> Option { self.with_element_state(cx, |_this, element_state, cx| { if let Some(child) = request_layout.child_element.as_mut() { @@ -245,7 +245,7 @@ impl Element for PopoverMenu { _: Bounds, request_layout: &mut Self::RequestLayoutState, child_hitbox: &mut Option, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { self.with_element_state(cx, |_this, _element_state, cx| { if let Some(mut child) = request_layout.child_element.take() { diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 5382236271..e656835c1d 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -2,9 +2,8 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ anchored, deferred, div, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, - Element, ElementContext, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, - ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, - WindowContext, + Element, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView, + MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext, }; pub struct RightClickMenu { @@ -41,8 +40,8 @@ impl RightClickMenu { fn with_element_state( &mut self, - cx: &mut ElementContext, - f: impl FnOnce(&mut Self, &mut MenuHandleElementState, &mut ElementContext) -> R, + cx: &mut WindowContext, + f: impl FnOnce(&mut Self, &mut MenuHandleElementState, &mut WindowContext) -> R, ) -> R { cx.with_element_state::, _>( Some(self.id.clone()), @@ -89,19 +88,24 @@ impl Default for MenuHandleElementState { } } -pub struct MenuHandleFrameState { +pub struct RequestLayoutState { child_layout_id: Option, child_element: Option, menu_element: Option, } +pub struct PrepaintState { + hitbox: Hitbox, + child_bounds: Option>, +} + impl Element for RightClickMenu { - type RequestLayoutState = MenuHandleFrameState; - type PrepaintState = Hitbox; + type RequestLayoutState = RequestLayoutState; + type PrepaintState = PrepaintState; fn request_layout( &mut self, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { self.with_element_state(cx, |this, element_state, cx| { let mut menu_layout_id = None; @@ -137,7 +141,7 @@ impl Element for RightClickMenu { ( layout_id, - MenuHandleFrameState { + RequestLayoutState { child_element, child_layout_id, menu_element, @@ -150,8 +154,8 @@ impl Element for RightClickMenu { &mut self, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - cx: &mut ElementContext, - ) -> Hitbox { + cx: &mut WindowContext, + ) -> PrepaintState { cx.with_element_id(Some(self.id.clone()), |cx| { let hitbox = cx.insert_hitbox(bounds, false); @@ -163,7 +167,12 @@ impl Element for RightClickMenu { menu.prepaint(cx); } - hitbox + PrepaintState { + hitbox, + child_bounds: request_layout + .child_layout_id + .map(|layout_id| cx.layout_bounds(layout_id)), + } }) } @@ -171,8 +180,8 @@ impl Element for RightClickMenu { &mut self, _bounds: Bounds, request_layout: &mut Self::RequestLayoutState, - hitbox: &mut Self::PrepaintState, - cx: &mut ElementContext, + prepaint_state: &mut Self::PrepaintState, + cx: &mut WindowContext, ) { self.with_element_state(cx, |this, element_state, cx| { if let Some(mut child) = request_layout.child_element.take() { @@ -191,10 +200,9 @@ impl Element for RightClickMenu { let attach = this.attach; let menu = element_state.menu.clone(); let position = element_state.position.clone(); - let child_layout_id = request_layout.child_layout_id; - let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); + let child_bounds = prepaint_state.child_bounds; - let hitbox_id = hitbox.id; + let hitbox_id = prepaint_state.hitbox.id; cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble && event.button == MouseButton::Right @@ -219,7 +227,7 @@ impl Element for RightClickMenu { .detach(); cx.focus_view(&new_menu); *menu.borrow_mut() = Some(new_menu); - *position.borrow_mut() = if child_layout_id.is_some() { + *position.borrow_mut() = if let Some(child_bounds) = child_bounds { if let Some(attach) = attach { attach.corner(child_bounds) } else { diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 5cf56bc58d..9a0f0ed1d2 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, ElementContext, - ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, - ViewContext, WindowContext, + div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, + InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext, + WindowContext, }; pub use crate::clickable::*; diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index faf25457d9..9ad7acf734 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -759,7 +759,7 @@ mod element { fn layout_handle( axis: Axis, pane_bounds: Bounds, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> PaneAxisHandleLayout { let handle_bounds = Bounds { origin: pane_bounds.origin.apply_along(axis, |origin| { @@ -797,7 +797,7 @@ mod element { fn request_layout( &mut self, - cx: &mut ui::prelude::ElementContext, + cx: &mut ui::prelude::WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { let mut style = Style::default(); style.flex_grow = 1.; @@ -812,7 +812,7 @@ mod element { &mut self, bounds: Bounds, _state: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) -> PaneAxisLayout { let dragged_handle = cx.with_element_state::>>, _>( Some(self.basis.into()), @@ -900,7 +900,7 @@ mod element { bounds: gpui::Bounds, _: &mut Self::RequestLayoutState, layout: &mut Self::PrepaintState, - cx: &mut ui::prelude::ElementContext, + cx: &mut ui::prelude::WindowContext, ) { for child in &mut layout.children { child.element.paint(cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 94890bc15c..8e29ce22e0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -78,9 +78,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings}; pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; use ui::{ - div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder, - InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString, - Styled as _, ViewContext, VisualContext as _, WindowContext, + div, h_flex, Context as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement, + Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _, + WindowContext, }; use util::{maybe, ResultExt}; use uuid::Uuid; @@ -4991,7 +4991,7 @@ impl Element for DisconnectedOverlay { type RequestLayoutState = AnyElement; type PrepaintState = (); - fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) { + fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) { let mut background = cx.theme().colors().elevated_surface_background; background.fade_out(0.2); let mut overlay = div() @@ -5016,7 +5016,7 @@ impl Element for DisconnectedOverlay { &mut self, bounds: Bounds, overlay: &mut Self::RequestLayoutState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { cx.insert_hitbox(bounds, true); overlay.prepaint(cx); @@ -5027,7 +5027,7 @@ impl Element for DisconnectedOverlay { _: Bounds, overlay: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, - cx: &mut ElementContext, + cx: &mut WindowContext, ) { overlay.paint(cx) }