Simplify element types (#3318)

Mikayla Maki created

This PR does away with the extra type parameters on Div and instead
introduces two wrapper elements, `Stateful` and `Focusable`. All of the
interactivity is stored on `Interactivity` and `InteractiveState`, which
is stored on the base element. The wrappers simply control what methods
are available to call.

Not sure this is fully working, but a smoke test does work.

/cc @as-cii @ConradIrwin 

Release Notes:

- N/A

Change summary

crates/command_palette2/src/command_palette.rs      |    6 
crates/editor2/src/editor.rs                        |   29 
crates/editor2/src/element.rs                       |  408 +-
crates/editor2/src/items.rs                         |    2 
crates/file_finder2/src/file_finder.rs              |    9 
crates/go_to_line2/src/go_to_line.rs                |    6 
crates/gpui2/docs/contexts.md                       |    0 
crates/gpui2/docs/key_dispatch.md                   |  101 
crates/gpui2/src/app.rs                             |    2 
crates/gpui2/src/element.rs                         |   14 
crates/gpui2/src/elements/div.rs                    | 1566 ++++++++++++--
crates/gpui2/src/elements/img.rs                    |  176 -
crates/gpui2/src/elements/mod.rs                    |    0 
crates/gpui2/src/elements/svg.rs                    |  131 
crates/gpui2/src/elements/text.rs                   |    2 
crates/gpui2/src/elements/uniform_list.rs           |  223 +
crates/gpui2/src/gpui2.rs                           |   16 
crates/gpui2/src/interactive.rs                     |  981 ---------
crates/gpui2/src/key_dispatch.rs                    |  265 --
crates/gpui2/src/keymap/matcher.rs                  |    1 
crates/gpui2/src/prelude.rs                         |    5 
crates/gpui2/src/style.rs                           |   42 
crates/gpui2/src/styled.rs                          |  291 --
crates/gpui2/src/view.rs                            |    8 
crates/gpui2/src/window.rs                          |   61 
crates/gpui2_macros/src/style_helpers.rs            |    4 
crates/picker2/src/picker2.rs                       |    6 
crates/project_panel2/src/project_panel.rs          |   14 
crates/storybook2/src/stories/colors.rs             |    2 
crates/storybook2/src/stories/focus.rs              |   11 
crates/storybook2/src/stories/kitchen_sink.rs       |    4 
crates/storybook2/src/stories/picker.rs             |    8 
crates/storybook2/src/stories/scroll.rs             |    7 
crates/storybook2/src/stories/text.rs               |    2 
crates/theme2/src/story.rs                          |    2 
crates/theme2/src/styles/players.rs                 |    2 
crates/ui2/src/components/button.rs                 |    2 
crates/ui2/src/components/checkbox.rs               |    6 
crates/ui2/src/components/elevated_surface.rs       |    2 
crates/ui2/src/components/icon_button.rs            |    4 
crates/ui2/src/components/input.rs                  |    5 
crates/ui2/src/components/modal.rs                  |    2 
crates/ui2/src/components/palette.rs                |    4 
crates/ui2/src/components/panel.rs                  |    6 
crates/ui2/src/components/tab.rs                    |    2 
crates/ui2/src/components/toast.rs                  |    7 
crates/ui2/src/components/toggle.rs                 |    2 
crates/ui2/src/prelude.rs                           |    4 
crates/ui2/src/styled_ext.rs                        |   11 
crates/ui2/src/to_extract/assistant_panel.rs        |    2 
crates/ui2/src/to_extract/breadcrumb.rs             |    6 
crates/ui2/src/to_extract/chat_panel.rs             |    5 
crates/ui2/src/to_extract/collab_panel.rs           |    5 
crates/ui2/src/to_extract/editor_pane.rs            |    4 
crates/ui2/src/to_extract/notifications_panel.rs    |    9 
crates/ui2/src/to_extract/panes.rs                  |    2 
crates/ui2/src/to_extract/project_panel.rs          |    5 
crates/ui2/src/to_extract/status_bar.rs             |   10 
crates/ui2/src/to_extract/tab_bar.rs                |    4 
crates/ui2/src/to_extract/title_bar.rs              |   10 
crates/workspace2/src/dock.rs                       |    2 
crates/workspace2/src/modal_layer.rs                |    4 
crates/workspace2/src/pane.rs                       |    6 
crates/workspace2/src/pane/dragged_item_receiver.rs |    2 
crates/workspace2/src/status_bar.rs                 |    2 
crates/workspace2/src/workspace2.rs                 |   28 
66 files changed, 2,129 insertions(+), 2,441 deletions(-)

Detailed changes

crates/command_palette2/src/command_palette.rs 🔗

@@ -1,9 +1,9 @@
 use collections::{CommandPaletteFilter, HashMap};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, div, Action, AppContext, Component, Div, EventEmitter, FocusHandle, Keystroke,
-    ParentElement, Render, StatelessInteractive, Styled, View, ViewContext, VisualContext,
-    WeakView, WindowContext,
+    actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle,
+    Keystroke, ParentComponent, Render, Styled, View, ViewContext, VisualContext, WeakView,
+    WindowContext,
 };
 use picker::{Picker, PickerDelegate};
 use std::{

crates/editor2/src/editor.rs 🔗

@@ -39,12 +39,12 @@ use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use git::diff_hunk_to_display;
 use gpui::{
-    action, actions, div, point, px, relative, rems, size, uniform_list, AnyElement, AppContext,
-    AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
+    action, actions, div, point, prelude::*, px, relative, rems, size, uniform_list, AnyElement,
+    AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
     EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
-    InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
-    StatefulInteractive, StatelessInteractive, Styled, Subscription, Task, TextStyle,
-    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
+    InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
+    Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
+    WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -9413,14 +9413,17 @@ impl Render for Editor {
             EditorMode::Full => cx.theme().colors().editor_background,
         };
 
-        EditorElement::new(EditorStyle {
-            background,
-            local_player: cx.theme().players().local(),
-            text: text_style,
-            scrollbar_width: px(12.),
-            syntax: cx.theme().syntax().clone(),
-            diagnostic_style: cx.theme().diagnostic_style(),
-        })
+        EditorElement::new(
+            cx.view(),
+            EditorStyle {
+                background,
+                local_player: cx.theme().players().local(),
+                text: text_style,
+                scrollbar_width: px(12.),
+                syntax: cx.theme().syntax().clone(),
+                diagnostic_style: cx.theme().diagnostic_style(),
+            },
+        )
     }
 }
 

crates/editor2/src/element.rs 🔗

@@ -20,9 +20,9 @@ use collections::{BTreeMap, HashMap};
 use gpui::{
     point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowWindow,
     Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
-    ElementInputHandler, Entity, Hsla, Line, MouseButton, MouseDownEvent, MouseMoveEvent,
-    MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, Size, Style, Styled, TextRun, TextStyle,
-    ViewContext, WindowContext,
+    ElementInputHandler, Entity, EntityId, Hsla, Line, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, ParentComponent, Pixels, ScrollWheelEvent, Size, Style, Styled, TextRun,
+    TextStyle, View, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -111,12 +111,16 @@ impl SelectionLayout {
 }
 
 pub struct EditorElement {
+    editor_id: EntityId,
     style: EditorStyle,
 }
 
 impl EditorElement {
-    pub fn new(style: EditorStyle) -> Self {
-        Self { style }
+    pub fn new(editor: &View<Editor>, style: EditorStyle) -> Self {
+        Self {
+            editor_id: editor.entity_id(),
+            style,
+        }
     }
 
     fn mouse_down(
@@ -622,7 +626,7 @@ impl EditorElement {
         let line_end_overshoot = 0.15 * layout.position_map.line_height;
         let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
 
-        cx.with_content_mask(ContentMask { bounds }, |cx| {
+        cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
             // todo!("cursor region")
             // cx.scene().push_cursor_region(CursorRegion {
             //     bounds,
@@ -2404,8 +2408,8 @@ enum Invisible {
 impl Element<Editor> for EditorElement {
     type ElementState = ();
 
-    fn id(&self) -> Option<gpui::ElementId> {
-        None
+    fn element_id(&self) -> Option<gpui::ElementId> {
+        Some(self.editor_id.into())
     }
 
     fn initialize(
@@ -2415,178 +2419,6 @@ impl Element<Editor> for EditorElement {
         cx: &mut gpui::ViewContext<Editor>,
     ) -> Self::ElementState {
         editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
-
-        let dispatch_context = editor.dispatch_context(cx);
-        cx.with_element_id(cx.view().entity_id(), |global_id, cx| {
-            cx.with_key_dispatch(
-                dispatch_context,
-                Some(editor.focus_handle.clone()),
-                |_, cx| {
-                    register_action(cx, Editor::move_left);
-                    register_action(cx, Editor::move_right);
-                    register_action(cx, Editor::move_down);
-                    register_action(cx, Editor::move_up);
-                    // on_action(cx, Editor::new_file); todo!()
-                    // on_action(cx, Editor::new_file_in_direction); todo!()
-                    register_action(cx, Editor::cancel);
-                    register_action(cx, Editor::newline);
-                    register_action(cx, Editor::newline_above);
-                    register_action(cx, Editor::newline_below);
-                    register_action(cx, Editor::backspace);
-                    register_action(cx, Editor::delete);
-                    register_action(cx, Editor::tab);
-                    register_action(cx, Editor::tab_prev);
-                    register_action(cx, Editor::indent);
-                    register_action(cx, Editor::outdent);
-                    register_action(cx, Editor::delete_line);
-                    register_action(cx, Editor::join_lines);
-                    register_action(cx, Editor::sort_lines_case_sensitive);
-                    register_action(cx, Editor::sort_lines_case_insensitive);
-                    register_action(cx, Editor::reverse_lines);
-                    register_action(cx, Editor::shuffle_lines);
-                    register_action(cx, Editor::convert_to_upper_case);
-                    register_action(cx, Editor::convert_to_lower_case);
-                    register_action(cx, Editor::convert_to_title_case);
-                    register_action(cx, Editor::convert_to_snake_case);
-                    register_action(cx, Editor::convert_to_kebab_case);
-                    register_action(cx, Editor::convert_to_upper_camel_case);
-                    register_action(cx, Editor::convert_to_lower_camel_case);
-                    register_action(cx, Editor::delete_to_previous_word_start);
-                    register_action(cx, Editor::delete_to_previous_subword_start);
-                    register_action(cx, Editor::delete_to_next_word_end);
-                    register_action(cx, Editor::delete_to_next_subword_end);
-                    register_action(cx, Editor::delete_to_beginning_of_line);
-                    register_action(cx, Editor::delete_to_end_of_line);
-                    register_action(cx, Editor::cut_to_end_of_line);
-                    register_action(cx, Editor::duplicate_line);
-                    register_action(cx, Editor::move_line_up);
-                    register_action(cx, Editor::move_line_down);
-                    register_action(cx, Editor::transpose);
-                    register_action(cx, Editor::cut);
-                    register_action(cx, Editor::copy);
-                    register_action(cx, Editor::paste);
-                    register_action(cx, Editor::undo);
-                    register_action(cx, Editor::redo);
-                    register_action(cx, Editor::move_page_up);
-                    register_action(cx, Editor::move_page_down);
-                    register_action(cx, Editor::next_screen);
-                    register_action(cx, Editor::scroll_cursor_top);
-                    register_action(cx, Editor::scroll_cursor_center);
-                    register_action(cx, Editor::scroll_cursor_bottom);
-                    register_action(cx, |editor, _: &LineDown, cx| {
-                        editor.scroll_screen(&ScrollAmount::Line(1.), cx)
-                    });
-                    register_action(cx, |editor, _: &LineUp, cx| {
-                        editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
-                    });
-                    register_action(cx, |editor, _: &HalfPageDown, cx| {
-                        editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
-                    });
-                    register_action(cx, |editor, _: &HalfPageUp, cx| {
-                        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
-                    });
-                    register_action(cx, |editor, _: &PageDown, cx| {
-                        editor.scroll_screen(&ScrollAmount::Page(1.), cx)
-                    });
-                    register_action(cx, |editor, _: &PageUp, cx| {
-                        editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
-                    });
-                    register_action(cx, Editor::move_to_previous_word_start);
-                    register_action(cx, Editor::move_to_previous_subword_start);
-                    register_action(cx, Editor::move_to_next_word_end);
-                    register_action(cx, Editor::move_to_next_subword_end);
-                    register_action(cx, Editor::move_to_beginning_of_line);
-                    register_action(cx, Editor::move_to_end_of_line);
-                    register_action(cx, Editor::move_to_start_of_paragraph);
-                    register_action(cx, Editor::move_to_end_of_paragraph);
-                    register_action(cx, Editor::move_to_beginning);
-                    register_action(cx, Editor::move_to_end);
-                    register_action(cx, Editor::select_up);
-                    register_action(cx, Editor::select_down);
-                    register_action(cx, Editor::select_left);
-                    register_action(cx, Editor::select_right);
-                    register_action(cx, Editor::select_to_previous_word_start);
-                    register_action(cx, Editor::select_to_previous_subword_start);
-                    register_action(cx, Editor::select_to_next_word_end);
-                    register_action(cx, Editor::select_to_next_subword_end);
-                    register_action(cx, Editor::select_to_beginning_of_line);
-                    register_action(cx, Editor::select_to_end_of_line);
-                    register_action(cx, Editor::select_to_start_of_paragraph);
-                    register_action(cx, Editor::select_to_end_of_paragraph);
-                    register_action(cx, Editor::select_to_beginning);
-                    register_action(cx, Editor::select_to_end);
-                    register_action(cx, Editor::select_all);
-                    register_action(cx, |editor, action, cx| {
-                        editor.select_all_matches(action, cx).log_err();
-                    });
-                    register_action(cx, Editor::select_line);
-                    register_action(cx, Editor::split_selection_into_lines);
-                    register_action(cx, Editor::add_selection_above);
-                    register_action(cx, Editor::add_selection_below);
-                    register_action(cx, |editor, action, cx| {
-                        editor.select_next(action, cx).log_err();
-                    });
-                    register_action(cx, |editor, action, cx| {
-                        editor.select_previous(action, cx).log_err();
-                    });
-                    register_action(cx, Editor::toggle_comments);
-                    register_action(cx, Editor::select_larger_syntax_node);
-                    register_action(cx, Editor::select_smaller_syntax_node);
-                    register_action(cx, Editor::move_to_enclosing_bracket);
-                    register_action(cx, Editor::undo_selection);
-                    register_action(cx, Editor::redo_selection);
-                    register_action(cx, Editor::go_to_diagnostic);
-                    register_action(cx, Editor::go_to_prev_diagnostic);
-                    register_action(cx, Editor::go_to_hunk);
-                    register_action(cx, Editor::go_to_prev_hunk);
-                    register_action(cx, Editor::go_to_definition);
-                    register_action(cx, Editor::go_to_definition_split);
-                    register_action(cx, Editor::go_to_type_definition);
-                    register_action(cx, Editor::go_to_type_definition_split);
-                    register_action(cx, Editor::fold);
-                    register_action(cx, Editor::fold_at);
-                    register_action(cx, Editor::unfold_lines);
-                    register_action(cx, Editor::unfold_at);
-                    register_action(cx, Editor::fold_selected_ranges);
-                    register_action(cx, Editor::show_completions);
-                    register_action(cx, Editor::toggle_code_actions);
-                    // on_action(cx, Editor::open_excerpts); todo!()
-                    register_action(cx, Editor::toggle_soft_wrap);
-                    register_action(cx, Editor::toggle_inlay_hints);
-                    register_action(cx, Editor::reveal_in_finder);
-                    register_action(cx, Editor::copy_path);
-                    register_action(cx, Editor::copy_relative_path);
-                    register_action(cx, Editor::copy_highlight_json);
-                    register_action(cx, |editor, action, cx| {
-                        editor
-                            .format(action, cx)
-                            .map(|task| task.detach_and_log_err(cx));
-                    });
-                    register_action(cx, Editor::restart_language_server);
-                    register_action(cx, Editor::show_character_palette);
-                    // on_action(cx, Editor::confirm_completion); todo!()
-                    register_action(cx, |editor, action, cx| {
-                        editor
-                            .confirm_code_action(action, cx)
-                            .map(|task| task.detach_and_log_err(cx));
-                    });
-                    // on_action(cx, Editor::rename); todo!()
-                    // on_action(cx, Editor::confirm_rename); todo!()
-                    register_action(cx, |editor, action, cx| {
-                        editor
-                            .find_all_references(action, cx)
-                            .map(|task| task.detach_and_log_err(cx));
-                    });
-                    register_action(cx, Editor::next_copilot_suggestion);
-                    register_action(cx, Editor::previous_copilot_suggestion);
-                    register_action(cx, Editor::copilot_suggest);
-                    register_action(cx, Editor::context_menu_first);
-                    register_action(cx, Editor::context_menu_prev);
-                    register_action(cx, Editor::context_menu_next);
-                    register_action(cx, Editor::context_menu_last);
-                },
-            )
-        });
     }
 
     fn layout(
@@ -2623,32 +2455,200 @@ impl Element<Editor> for EditorElement {
             size: layout.text_size,
         };
 
-        // We call with_z_index to establish a new stacking context.
-        cx.with_z_index(0, |cx| {
-            cx.with_content_mask(ContentMask { bounds }, |cx| {
-                self.paint_mouse_listeners(
-                    bounds,
-                    gutter_bounds,
-                    text_bounds,
-                    &layout.position_map,
-                    cx,
-                );
-
-                self.paint_background(gutter_bounds, text_bounds, &layout, cx);
-                if layout.gutter_size.width > Pixels::ZERO {
-                    self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
-                }
-
-                self.paint_text(text_bounds, &mut layout, editor, cx);
+        let dispatch_context = editor.dispatch_context(cx);
+        cx.with_key_dispatch(
+            dispatch_context,
+            Some(editor.focus_handle.clone()),
+            |_, cx| {
+                register_action(cx, Editor::move_left);
+                register_action(cx, Editor::move_right);
+                register_action(cx, Editor::move_down);
+                register_action(cx, Editor::move_up);
+                // on_action(cx, Editor::new_file); todo!()
+                // on_action(cx, Editor::new_file_in_direction); todo!()
+                register_action(cx, Editor::cancel);
+                register_action(cx, Editor::newline);
+                register_action(cx, Editor::newline_above);
+                register_action(cx, Editor::newline_below);
+                register_action(cx, Editor::backspace);
+                register_action(cx, Editor::delete);
+                register_action(cx, Editor::tab);
+                register_action(cx, Editor::tab_prev);
+                register_action(cx, Editor::indent);
+                register_action(cx, Editor::outdent);
+                register_action(cx, Editor::delete_line);
+                register_action(cx, Editor::join_lines);
+                register_action(cx, Editor::sort_lines_case_sensitive);
+                register_action(cx, Editor::sort_lines_case_insensitive);
+                register_action(cx, Editor::reverse_lines);
+                register_action(cx, Editor::shuffle_lines);
+                register_action(cx, Editor::convert_to_upper_case);
+                register_action(cx, Editor::convert_to_lower_case);
+                register_action(cx, Editor::convert_to_title_case);
+                register_action(cx, Editor::convert_to_snake_case);
+                register_action(cx, Editor::convert_to_kebab_case);
+                register_action(cx, Editor::convert_to_upper_camel_case);
+                register_action(cx, Editor::convert_to_lower_camel_case);
+                register_action(cx, Editor::delete_to_previous_word_start);
+                register_action(cx, Editor::delete_to_previous_subword_start);
+                register_action(cx, Editor::delete_to_next_word_end);
+                register_action(cx, Editor::delete_to_next_subword_end);
+                register_action(cx, Editor::delete_to_beginning_of_line);
+                register_action(cx, Editor::delete_to_end_of_line);
+                register_action(cx, Editor::cut_to_end_of_line);
+                register_action(cx, Editor::duplicate_line);
+                register_action(cx, Editor::move_line_up);
+                register_action(cx, Editor::move_line_down);
+                register_action(cx, Editor::transpose);
+                register_action(cx, Editor::cut);
+                register_action(cx, Editor::copy);
+                register_action(cx, Editor::paste);
+                register_action(cx, Editor::undo);
+                register_action(cx, Editor::redo);
+                register_action(cx, Editor::move_page_up);
+                register_action(cx, Editor::move_page_down);
+                register_action(cx, Editor::next_screen);
+                register_action(cx, Editor::scroll_cursor_top);
+                register_action(cx, Editor::scroll_cursor_center);
+                register_action(cx, Editor::scroll_cursor_bottom);
+                register_action(cx, |editor, _: &LineDown, cx| {
+                    editor.scroll_screen(&ScrollAmount::Line(1.), cx)
+                });
+                register_action(cx, |editor, _: &LineUp, cx| {
+                    editor.scroll_screen(&ScrollAmount::Line(-1.), cx)
+                });
+                register_action(cx, |editor, _: &HalfPageDown, cx| {
+                    editor.scroll_screen(&ScrollAmount::Page(0.5), cx)
+                });
+                register_action(cx, |editor, _: &HalfPageUp, cx| {
+                    editor.scroll_screen(&ScrollAmount::Page(-0.5), cx)
+                });
+                register_action(cx, |editor, _: &PageDown, cx| {
+                    editor.scroll_screen(&ScrollAmount::Page(1.), cx)
+                });
+                register_action(cx, |editor, _: &PageUp, cx| {
+                    editor.scroll_screen(&ScrollAmount::Page(-1.), cx)
+                });
+                register_action(cx, Editor::move_to_previous_word_start);
+                register_action(cx, Editor::move_to_previous_subword_start);
+                register_action(cx, Editor::move_to_next_word_end);
+                register_action(cx, Editor::move_to_next_subword_end);
+                register_action(cx, Editor::move_to_beginning_of_line);
+                register_action(cx, Editor::move_to_end_of_line);
+                register_action(cx, Editor::move_to_start_of_paragraph);
+                register_action(cx, Editor::move_to_end_of_paragraph);
+                register_action(cx, Editor::move_to_beginning);
+                register_action(cx, Editor::move_to_end);
+                register_action(cx, Editor::select_up);
+                register_action(cx, Editor::select_down);
+                register_action(cx, Editor::select_left);
+                register_action(cx, Editor::select_right);
+                register_action(cx, Editor::select_to_previous_word_start);
+                register_action(cx, Editor::select_to_previous_subword_start);
+                register_action(cx, Editor::select_to_next_word_end);
+                register_action(cx, Editor::select_to_next_subword_end);
+                register_action(cx, Editor::select_to_beginning_of_line);
+                register_action(cx, Editor::select_to_end_of_line);
+                register_action(cx, Editor::select_to_start_of_paragraph);
+                register_action(cx, Editor::select_to_end_of_paragraph);
+                register_action(cx, Editor::select_to_beginning);
+                register_action(cx, Editor::select_to_end);
+                register_action(cx, Editor::select_all);
+                register_action(cx, |editor, action, cx| {
+                    editor.select_all_matches(action, cx).log_err();
+                });
+                register_action(cx, Editor::select_line);
+                register_action(cx, Editor::split_selection_into_lines);
+                register_action(cx, Editor::add_selection_above);
+                register_action(cx, Editor::add_selection_below);
+                register_action(cx, |editor, action, cx| {
+                    editor.select_next(action, cx).log_err();
+                });
+                register_action(cx, |editor, action, cx| {
+                    editor.select_previous(action, cx).log_err();
+                });
+                register_action(cx, Editor::toggle_comments);
+                register_action(cx, Editor::select_larger_syntax_node);
+                register_action(cx, Editor::select_smaller_syntax_node);
+                register_action(cx, Editor::move_to_enclosing_bracket);
+                register_action(cx, Editor::undo_selection);
+                register_action(cx, Editor::redo_selection);
+                register_action(cx, Editor::go_to_diagnostic);
+                register_action(cx, Editor::go_to_prev_diagnostic);
+                register_action(cx, Editor::go_to_hunk);
+                register_action(cx, Editor::go_to_prev_hunk);
+                register_action(cx, Editor::go_to_definition);
+                register_action(cx, Editor::go_to_definition_split);
+                register_action(cx, Editor::go_to_type_definition);
+                register_action(cx, Editor::go_to_type_definition_split);
+                register_action(cx, Editor::fold);
+                register_action(cx, Editor::fold_at);
+                register_action(cx, Editor::unfold_lines);
+                register_action(cx, Editor::unfold_at);
+                register_action(cx, Editor::fold_selected_ranges);
+                register_action(cx, Editor::show_completions);
+                register_action(cx, Editor::toggle_code_actions);
+                // on_action(cx, Editor::open_excerpts); todo!()
+                register_action(cx, Editor::toggle_soft_wrap);
+                register_action(cx, Editor::toggle_inlay_hints);
+                register_action(cx, Editor::reveal_in_finder);
+                register_action(cx, Editor::copy_path);
+                register_action(cx, Editor::copy_relative_path);
+                register_action(cx, Editor::copy_highlight_json);
+                register_action(cx, |editor, action, cx| {
+                    editor
+                        .format(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                });
+                register_action(cx, Editor::restart_language_server);
+                register_action(cx, Editor::show_character_palette);
+                // on_action(cx, Editor::confirm_completion); todo!()
+                register_action(cx, |editor, action, cx| {
+                    editor
+                        .confirm_code_action(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                });
+                // on_action(cx, Editor::rename); todo!()
+                // on_action(cx, Editor::confirm_rename); todo!()
+                register_action(cx, |editor, action, cx| {
+                    editor
+                        .find_all_references(action, cx)
+                        .map(|task| task.detach_and_log_err(cx));
+                });
+                register_action(cx, Editor::next_copilot_suggestion);
+                register_action(cx, Editor::previous_copilot_suggestion);
+                register_action(cx, Editor::copilot_suggest);
+                register_action(cx, Editor::context_menu_first);
+                register_action(cx, Editor::context_menu_prev);
+                register_action(cx, Editor::context_menu_next);
+                register_action(cx, Editor::context_menu_last);
+
+                // We call with_z_index to establish a new stacking context.
+                cx.with_z_index(0, |cx| {
+                    cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
+                        self.paint_mouse_listeners(
+                            bounds,
+                            gutter_bounds,
+                            text_bounds,
+                            &layout.position_map,
+                            cx,
+                        );
+                        self.paint_background(gutter_bounds, text_bounds, &layout, cx);
+                        if layout.gutter_size.width > Pixels::ZERO {
+                            self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
+                        }
+                        self.paint_text(text_bounds, &mut layout, editor, cx);
 
-                if !layout.blocks.is_empty() {
-                    self.paint_blocks(bounds, &mut layout, editor, cx);
-                }
+                        if !layout.blocks.is_empty() {
+                            self.paint_blocks(bounds, &mut layout, editor, cx);
+                        }
 
-                let input_handler = ElementInputHandler::new(bounds, cx);
-                cx.handle_input(&editor.focus_handle, input_handler);
-            });
-        });
+                        let input_handler = ElementInputHandler::new(bounds, cx);
+                        cx.handle_input(&editor.focus_handle, input_handler);
+                    });
+                });
+            },
+        )
     }
 }
 

crates/editor2/src/items.rs 🔗

@@ -9,7 +9,7 @@ use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
     div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
-    FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
+    FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View,
     ViewContext, VisualContext, WeakView,
 };
 use language::{

crates/file_finder2/src/file_finder.rs 🔗

@@ -2,8 +2,9 @@ use collections::HashMap;
 use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
 use gpui::{
-    actions, div, AppContext, Component, Div, EventEmitter, Model, ParentElement, Render,
-    StatelessInteractive, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    actions, div, AppContext, Component, Div, EventEmitter, InteractiveComponent, Model,
+    ParentComponent, Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
+    WindowContext,
 };
 use picker::{Picker, PickerDelegate};
 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -32,9 +33,7 @@ pub fn init(cx: &mut AppContext) {
 
 impl FileFinder {
     fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
-        dbg!("REGISTERING");
         workspace.register_action(|workspace, _: &Toggle, cx| {
-            dbg!("CALLING ACTION");
             let Some(file_finder) = workspace.current_modal::<Self>(cx) else {
                 Self::open(workspace, cx);
                 return;
@@ -593,7 +592,6 @@ impl PickerDelegate for FileFinderDelegate {
     }
 
     fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<FileFinderDelegate>>) {
-        dbg!("CONFIRMING???");
         if let Some(m) = self.matches.get(self.selected_index()) {
             if let Some(workspace) = self.workspace.upgrade() {
                 let open_task = workspace.update(cx, move |workspace, cx| {
@@ -691,7 +689,6 @@ impl PickerDelegate for FileFinderDelegate {
                                 .log_err();
                         }
                     }
-                    dbg!("DISMISSING");
                     finder
                         .update(&mut cx, |_, cx| cx.emit(ModalEvent::Dismissed))
                         .ok()?;

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,7 +1,7 @@
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
-    StatelessInteractive, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
+    actions, div, prelude::*, AppContext, Div, EventEmitter, ParentComponent, Render, SharedString,
+    Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
 };
 use text::{Bias, Point};
 use theme::ActiveTheme;
@@ -150,7 +150,7 @@ impl Render for GoToLine {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
             .elevation_2(cx)
-            .context("GoToLine")
+            .key_context("GoToLine")
             .on_action(Self::cancel)
             .on_action(Self::confirm)
             .w_96()

crates/gpui2/docs/key_dispatch.md 🔗

@@ -0,0 +1,101 @@
+# Key Dispatch
+
+GPUI is designed for keyboard-first interactivity.
+
+To expose functionality to the mouse, you render a button with a click handler.
+
+To expose functionality to the keyboard, you bind an *action* in a *key context*.
+
+Actions are similar to framework-level events like `MouseDown`, `KeyDown`, etc, but you can define them yourself:
+
+```rust
+mod menu {
+    #[gpui::action]
+    struct MoveUp;
+
+    #[gpui::action]
+    struct MoveDown;
+}
+```
+
+Actions are frequently unit structs, for which we have a macro. The above could also be written:
+
+```rust
+mod menu {
+    actions!(MoveUp, MoveDown);
+}
+```
+
+Actions can also be more complex types:
+
+```rust
+mod menu {
+    #[gpui::action]
+    struct Move {
+        direction: Direction,
+        select: bool,
+    }
+}
+```
+
+To bind actions, chain `on_action` on to your element:
+
+```rust
+impl Render for Menu {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component {
+        div()
+            .on_action(|this: &mut Menu, move: &MoveUp, cx: &mut ViewContext<Menu>| {
+                // ...
+            })
+            .on_action(|this, move: &MoveDown, cx| {
+                // ...
+            })
+            .children(todo!())
+    }
+}
+```
+
+In order to bind keys to actions, you need to declare a *key context* for part of the element tree by calling `key_context`.
+
+```rust
+impl Render for Menu {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component {
+        div()
+            .key_context("menu")
+            .on_action(|this: &mut Menu, move: &MoveUp, cx: &mut ViewContext<Menu>| {
+                // ...
+            })
+            .on_action(|this, move: &MoveDown, cx| {
+                // ...
+            })
+            .children(todo!())
+    }
+}
+```
+
+Now you can target your context in the keymap. Note how actions are identified in the keymap by their fully-qualified type name.
+
+```json
+{
+  "context": "menu",
+  "bindings": {
+    "up": "menu::MoveUp",
+    "down": "menu::MoveDown"
+  }
+}
+```
+
+If you had opted for the more complex type definition, you'd provide the serialized representation of the action alongside the name:
+
+```json
+{
+  "context": "menu",
+  "bindings": {
+    "up": ["menu::Move", {direction: "up", select: false}]
+    "down": ["menu::Move", {direction: "down", select: false}]
+    "shift-up": ["menu::Move", {direction: "up", select: true}]
+    "shift-down": ["menu::Move", {direction: "down", select: true}]
+  }
+}
+
+```

crates/gpui2/src/app.rs 🔗

@@ -1114,7 +1114,7 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
 
 /// Contains state associated with an active drag operation, started by dragging an element
 /// within the window or by dragging into the app from the underlying platform.
-pub(crate) struct AnyDrag {
+pub struct AnyDrag {
     pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
 }

crates/gpui2/src/element.rs 🔗

@@ -8,7 +8,7 @@ use std::{any::Any, mem};
 pub trait Element<V: 'static> {
     type ElementState: 'static;
 
-    fn id(&self) -> Option<ElementId>;
+    fn element_id(&self) -> Option<ElementId>;
 
     /// Called to initialize this element for the current frame. If this
     /// element had state in a previous frame, it will be passed in for the 3rd argument.
@@ -38,7 +38,7 @@ pub trait Element<V: 'static> {
 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
 
-pub trait ParentElement<V: 'static> {
+pub trait ParentComponent<V: 'static> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
 
     fn child(mut self, child: impl Component<V>) -> Self
@@ -120,7 +120,7 @@ where
     E::ElementState: 'static,
 {
     fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
-        let frame_state = if let Some(id) = self.element.id() {
+        let frame_state = if let Some(id) = self.element.element_id() {
             cx.with_element_state(id, |element_state, cx| {
                 let element_state = self.element.initialize(view_state, element_state, cx);
                 ((), element_state)
@@ -142,7 +142,7 @@ where
                 frame_state: initial_frame_state,
             } => {
                 frame_state = initial_frame_state;
-                if let Some(id) = self.element.id() {
+                if let Some(id) = self.element.element_id() {
                     layout_id = cx.with_element_state(id, |element_state, cx| {
                         let mut element_state = element_state.unwrap();
                         let layout_id = self.element.layout(state, &mut element_state, cx);
@@ -181,7 +181,7 @@ where
                 ..
             } => {
                 let bounds = cx.layout_bounds(layout_id);
-                if let Some(id) = self.element.id() {
+                if let Some(id) = self.element.element_id() {
                     cx.with_element_state(id, |element_state, cx| {
                         let mut element_state = element_state.unwrap();
                         self.element
@@ -255,7 +255,7 @@ where
         // Ignore the element offset when drawing this element, as the origin is already specified
         // in absolute terms.
         origin -= cx.element_offset();
-        cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx))
+        cx.with_element_offset(origin, |cx| self.paint(view_state, cx))
     }
 }
 
@@ -351,7 +351,7 @@ where
 {
     type ElementState = AnyElement<V>;
 
-    fn id(&self) -> Option<ElementId> {
+    fn element_id(&self) -> Option<ElementId> {
         None
     }
 

crates/gpui2/src/elements/div.rs 🔗

@@ -1,225 +1,606 @@
-use std::fmt::Debug;
-
 use crate::{
-    point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, ElementInteractivity,
-    FocusHandle, FocusListeners, Focusable, FocusableKeyDispatch, GlobalElementId, GroupBounds,
-    InteractiveElementState, KeyContext, KeyDispatch, LayoutId, NonFocusableKeyDispatch, Overflow,
-    ParentElement, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity,
-    StatelessInteractive, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext,
-    Visibility,
+    point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
+    BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent,
+    FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
+    MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent,
+    SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,
 };
+use collections::HashMap;
+use parking_lot::Mutex;
 use refineable::Refineable;
 use smallvec::SmallVec;
+use std::{
+    any::{Any, TypeId},
+    fmt::Debug,
+    marker::PhantomData,
+    mem,
+    sync::Arc,
+    time::Duration,
+};
+use taffy::style::Overflow;
 use util::ResultExt;
 
-pub struct Div<
-    V: 'static,
-    I: ElementInteractivity<V> = StatelessInteractivity<V>,
-    K: KeyDispatch<V> = NonFocusableKeyDispatch,
-> {
-    interactivity: I,
-    key_dispatch: K,
-    children: SmallVec<[AnyElement<V>; 2]>,
-    group: Option<SharedString>,
-    base_style: StyleRefinement,
+const DRAG_THRESHOLD: f64 = 2.;
+const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
+const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
+
+pub struct GroupStyle {
+    pub group: SharedString,
+    pub style: StyleRefinement,
 }
 
-pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
-    Div {
-        interactivity: StatelessInteractivity::default(),
-        key_dispatch: NonFocusableKeyDispatch::default(),
-        children: SmallVec::new(),
-        group: None,
-        base_style: StyleRefinement::default(),
+pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
+    fn interactivity(&mut self) -> &mut Interactivity<V>;
+
+    fn group(mut self, group: impl Into<SharedString>) -> Self {
+        self.interactivity().group = Some(group.into());
+        self
     }
-}
 
-impl<V, F> Div<V, StatelessInteractivity<V>, F>
-where
-    V: 'static,
-    F: KeyDispatch<V>,
-{
-    pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
-        Div {
-            interactivity: StatefulInteractivity::new(id.into(), self.interactivity),
-            key_dispatch: self.key_dispatch,
-            children: self.children,
-            group: self.group,
-            base_style: self.base_style,
+    fn id(mut self, id: impl Into<ElementId>) -> Stateful<V, Self> {
+        self.interactivity().element_id = Some(id.into());
+
+        Stateful {
+            element: self,
+            view_type: PhantomData,
+        }
+    }
+
+    fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<V, Self> {
+        self.interactivity().focusable = true;
+        self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
+        Focusable {
+            element: self,
+            view_type: PhantomData,
         }
     }
+
+    fn key_context<C, E>(mut self, key_context: C) -> Self
+    where
+        C: TryInto<KeyContext, Error = E>,
+        E: Debug,
+    {
+        if let Some(key_context) = key_context.try_into().log_err() {
+            self.interactivity().key_context = key_context;
+        }
+        self
+    }
+
+    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
+        self.interactivity().hover_style = f(StyleRefinement::default());
+        self
+    }
+
+    fn group_hover(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self {
+        self.interactivity().group_hover_style = Some(GroupStyle {
+            group: group_name.into(),
+            style: f(StyleRefinement::default()),
+        });
+        self
+    }
+
+    fn on_mouse_down(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_down_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(&event.position)
+                {
+                    handler(view, event, cx)
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_any_mouse_down(
+        mut self,
+        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_down_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    handler(view, event, cx)
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_mouse_up(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_up_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(&event.position)
+                {
+                    handler(view, event, cx)
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_any_mouse_up(
+        mut self,
+        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_up_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    handler(view, event, cx)
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_mouse_down_out(
+        mut self,
+        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_down_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
+                    handler(view, event, cx)
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_mouse_up_out(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_up_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.contains_point(&event.position)
+                {
+                    handler(view, event, cx);
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_mouse_move(
+        mut self,
+        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().mouse_move_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    handler(view, event, cx);
+                }
+            },
+        ));
+        self
+    }
+
+    fn on_scroll_wheel(
+        mut self,
+        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().scroll_wheel_listeners.push(Box::new(
+            move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    handler(view, event, cx);
+                }
+            },
+        ));
+        self
+    }
+
+    /// Capture the given action, fires during the capture phase
+    fn capture_action<A: Action>(
+        mut self,
+        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().action_listeners.push((
+            TypeId::of::<A>(),
+            Box::new(move |view, action, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Capture {
+                    listener(view, action, cx)
+                }
+            }),
+        ));
+        self
+    }
+
+    /// Add a listener for the given action, fires during the bubble event phase
+    fn on_action<A: Action>(
+        mut self,
+        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().action_listeners.push((
+            TypeId::of::<A>(),
+            Box::new(move |view, action, phase, cx| {
+                let action = action.downcast_ref().unwrap();
+                if phase == DispatchPhase::Bubble {
+                    listener(view, action, cx)
+                }
+            }),
+        ));
+        self
+    }
+
+    fn on_key_down(
+        mut self,
+        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity()
+            .key_down_listeners
+            .push(Box::new(move |view, event, phase, cx| {
+                listener(view, event, phase, cx)
+            }));
+        self
+    }
+
+    fn on_key_up(
+        mut self,
+        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity()
+            .key_up_listeners
+            .push(Box::new(move |view, event, phase, cx| {
+                listener(view, event, phase, cx)
+            }));
+        self
+    }
+
+    fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
+        self.interactivity()
+            .drag_over_styles
+            .push((TypeId::of::<S>(), f(StyleRefinement::default())));
+        self
+    }
+
+    fn group_drag_over<S: 'static>(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self {
+        self.interactivity().group_drag_over_styles.push((
+            TypeId::of::<S>(),
+            GroupStyle {
+                group: group_name.into(),
+                style: f(StyleRefinement::default()),
+            },
+        ));
+        self
+    }
+
+    fn on_drop<W: 'static>(
+        mut self,
+        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
+    ) -> Self {
+        self.interactivity().drop_listeners.push((
+            TypeId::of::<W>(),
+            Box::new(move |view, dragged_view, cx| {
+                listener(view, dragged_view.downcast().unwrap(), cx);
+            }),
+        ));
+        self
+    }
 }
 
-impl<V, I, F> Div<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    pub fn group(mut self, group: impl Into<SharedString>) -> Self {
-        self.group = Some(group.into());
+pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveComponent<V> {
+    fn focusable(mut self) -> Focusable<V, Self> {
+        self.interactivity().focusable = true;
+        Focusable {
+            element: self,
+            view_type: PhantomData,
+        }
+    }
+
+    fn overflow_scroll(mut self) -> Self {
+        self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
+        self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    fn overflow_x_scroll(mut self) -> Self {
+        self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
         self
     }
 
-    pub fn z_index(mut self, z_index: u32) -> Self {
-        self.base_style.z_index = Some(z_index);
+    fn overflow_y_scroll(mut self) -> Self {
+        self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
         self
     }
 
-    pub fn context<C>(mut self, context: C) -> Self
+    fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
-        C: TryInto<KeyContext>,
-        C::Error: Debug,
     {
-        if let Some(context) = context.try_into().log_err() {
-            *self.key_dispatch.key_context_mut() = context;
-        }
+        self.interactivity().active_style = f(StyleRefinement::default());
         self
     }
 
-    pub fn overflow_hidden(mut self) -> Self {
-        self.base_style.overflow.x = Some(Overflow::Hidden);
-        self.base_style.overflow.y = Some(Overflow::Hidden);
+    fn group_active(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().group_active_style = Some(GroupStyle {
+            group: group_name.into(),
+            style: f(StyleRefinement::default()),
+        });
         self
     }
 
-    pub fn overflow_hidden_x(mut self) -> Self {
-        self.base_style.overflow.x = Some(Overflow::Hidden);
+    fn on_click(
+        mut self,
+        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity()
+            .click_listeners
+            .push(Box::new(move |view, event, cx| listener(view, event, cx)));
         self
     }
 
-    pub fn overflow_hidden_y(mut self) -> Self {
-        self.base_style.overflow.y = Some(Overflow::Hidden);
+    fn on_drag<W>(
+        mut self,
+        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+        W: 'static + Render,
+    {
+        debug_assert!(
+            self.interactivity().drag_listener.is_none(),
+            "calling on_drag more than once on the same element is not supported"
+        );
+        self.interactivity().drag_listener =
+            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
+                view: listener(view_state, cx).into(),
+                cursor_offset,
+            }));
         self
     }
 
-    fn with_element_id<R>(
-        &mut self,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(&mut Self, Option<GlobalElementId>, &mut ViewContext<V>) -> R,
-    ) -> R {
-        if let Some(id) = self.id() {
-            cx.with_element_id(id, |global_id, cx| f(self, Some(global_id), cx))
-        } else {
-            f(self, None, cx)
-        }
+    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
+    where
+        Self: Sized,
+    {
+        debug_assert!(
+            self.interactivity().hover_listener.is_none(),
+            "calling on_hover more than once on the same element is not supported"
+        );
+        self.interactivity().hover_listener = Some(Box::new(listener));
+        self
     }
 
-    pub fn compute_style(
-        &self,
-        bounds: Bounds<Pixels>,
-        element_state: &DivState,
-        cx: &mut ViewContext<V>,
-    ) -> Style {
-        let mut computed_style = Style::default();
-        computed_style.refine(&self.base_style);
-        self.key_dispatch.refine_style(&mut computed_style, cx);
-        self.interactivity.refine_style(
-            &mut computed_style,
-            bounds,
-            &element_state.interactive,
-            cx,
+    fn tooltip<W>(
+        mut self,
+        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+        W: 'static + Render,
+    {
+        debug_assert!(
+            self.interactivity().tooltip_builder.is_none(),
+            "calling tooltip more than once on the same element is not supported"
         );
-        computed_style
+        self.interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
+            build_tooltip(view_state, cx).into()
+        }));
+
+        self
     }
 }
 
-impl<V: 'static> Div<V, StatefulInteractivity<V>, NonFocusableKeyDispatch> {
-    pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
-        Div {
-            interactivity: self.interactivity,
-            key_dispatch: FocusableKeyDispatch::new(self.key_dispatch),
-            children: self.children,
-            group: self.group,
-            base_style: self.base_style,
-        }
+pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> {
+    fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_style = f(StyleRefinement::default());
+        self
     }
 
-    pub fn track_focus(
-        self,
-        handle: &FocusHandle,
-    ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
-        Div {
-            interactivity: self.interactivity,
-            key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
-            children: self.children,
-            group: self.group,
-            base_style: self.base_style,
-        }
+    fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_in_style = f(StyleRefinement::default());
+        self
     }
 
-    pub fn overflow_scroll(mut self) -> Self {
-        self.base_style.overflow.x = Some(Overflow::Scroll);
-        self.base_style.overflow.y = Some(Overflow::Scroll);
+    fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().in_focus_style = f(StyleRefinement::default());
         self
     }
 
-    pub fn overflow_x_scroll(mut self) -> Self {
-        self.base_style.overflow.x = Some(Overflow::Scroll);
+    fn on_focus(
+        mut self,
+        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_listeners.push(Box::new(
+            move |view, focus_handle, event, cx| {
+                if event.focused.as_ref() == Some(focus_handle) {
+                    listener(view, event, cx)
+                }
+            },
+        ));
         self
     }
 
-    pub fn overflow_y_scroll(mut self) -> Self {
-        self.base_style.overflow.y = Some(Overflow::Scroll);
+    fn on_blur(
+        mut self,
+        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_listeners.push(Box::new(
+            move |view, focus_handle, event, cx| {
+                if event.blurred.as_ref() == Some(focus_handle) {
+                    listener(view, event, cx)
+                }
+            },
+        ));
         self
     }
-}
 
-impl<V: 'static> Div<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
-    pub fn track_focus(
-        self,
-        handle: &FocusHandle,
-    ) -> Div<V, StatefulInteractivity<V>, FocusableKeyDispatch<V>> {
-        Div {
-            interactivity: self.interactivity.into_stateful(handle),
-            key_dispatch: FocusableKeyDispatch::tracked(self.key_dispatch, handle),
-            children: self.children,
-            group: self.group,
-            base_style: self.base_style,
-        }
+    fn on_focus_in(
+        mut self,
+        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_listeners.push(Box::new(
+            move |view, focus_handle, event, cx| {
+                let descendant_blurred = event
+                    .blurred
+                    .as_ref()
+                    .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+                let descendant_focused = event
+                    .focused
+                    .as_ref()
+                    .map_or(false, |focused| focus_handle.contains(focused, cx));
+
+                if !descendant_blurred && descendant_focused {
+                    listener(view, event, cx)
+                }
+            },
+        ));
+        self
     }
-}
 
-impl<V, I> Focusable<V> for Div<V, I, FocusableKeyDispatch<V>>
-where
-    V: 'static,
-    I: ElementInteractivity<V>,
-{
-    fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
-        &mut self.key_dispatch.focus_listeners
+    fn on_focus_out(
+        mut self,
+        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interactivity().focus_listeners.push(Box::new(
+            move |view, focus_handle, event, cx| {
+                let descendant_blurred = event
+                    .blurred
+                    .as_ref()
+                    .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+                let descendant_focused = event
+                    .focused
+                    .as_ref()
+                    .map_or(false, |focused| focus_handle.contains(focused, cx));
+                if descendant_blurred && !descendant_focused {
+                    listener(view, event, cx)
+                }
+            },
+        ));
+        self
     }
+}
+
+pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
+
+pub type FocusListener<V> =
+    Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
+
+pub type MouseDownListener<V> = Box<
+    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+pub type MouseUpListener<V> = Box<
+    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+
+pub type MouseMoveListener<V> = Box<
+    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+
+pub type ScrollWheelListener<V> = Box<
+    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + 'static,
+>;
+
+pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
+
+pub type DragListener<V> =
+    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
+
+type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
+
+pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
+
+pub type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
 
-    fn set_focus_style(&mut self, style: StyleRefinement) {
-        self.key_dispatch.focus_style = style;
+pub type KeyDownListener<V> =
+    Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type KeyUpListener<V> =
+    Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type ActionListener<V> =
+    Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub fn div<V: 'static>() -> Div<V> {
+    Div {
+        interactivity: Interactivity::default(),
+        children: SmallVec::default(),
     }
+}
+
+pub struct Div<V> {
+    interactivity: Interactivity<V>,
+    children: SmallVec<[AnyElement<V>; 2]>,
+}
 
-    fn set_focus_in_style(&mut self, style: StyleRefinement) {
-        self.key_dispatch.focus_in_style = style;
+impl<V> Styled for Div<V> {
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.interactivity.base_style
     }
+}
 
-    fn set_in_focus_style(&mut self, style: StyleRefinement) {
-        self.key_dispatch.in_focus_style = style;
+impl<V: 'static> InteractiveComponent<V> for Div<V> {
+    fn interactivity(&mut self) -> &mut Interactivity<V> {
+        &mut self.interactivity
     }
 }
 
-#[derive(Default)]
-pub struct DivState {
-    interactive: InteractiveElementState,
-    focus_handle: Option<FocusHandle>,
-    child_layout_ids: SmallVec<[LayoutId; 4]>,
+impl<V: 'static> ParentComponent<V> for Div<V> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        &mut self.children
+    }
 }
 
-impl<V, I, F> Element<V> for Div<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    type ElementState = DivState;
+impl<V: 'static> Element<V> for Div<V> {
+    type ElementState = NodeState;
 
-    fn id(&self) -> Option<ElementId> {
-        self.interactivity
-            .as_stateful()
-            .map(|identified| identified.id.clone())
+    fn element_id(&self) -> Option<ElementId> {
+        self.interactivity.element_id.clone()
     }
 
     fn initialize(
@@ -228,21 +609,18 @@ where
         element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<V>,
     ) -> Self::ElementState {
-        let mut element_state = element_state.unwrap_or_default();
-        self.with_element_id(cx, |this, _global_id, cx| {
-            this.key_dispatch.initialize(
-                element_state.focus_handle.take(),
-                cx,
-                |focus_handle, cx| {
-                    this.interactivity.initialize(cx);
-                    element_state.focus_handle = focus_handle;
-                    for child in &mut this.children {
-                        child.initialize(view_state, cx);
-                    }
-                },
-            );
-        });
-        element_state
+        let interactive_state = self
+            .interactivity
+            .initialize(element_state.map(|s| s.interactive_state), cx);
+
+        for child in &mut self.children {
+            child.initialize(view_state, cx);
+        }
+
+        NodeState {
+            interactive_state,
+            child_layout_ids: SmallVec::new(),
+        }
     }
 
     fn layout(
@@ -250,19 +628,21 @@ where
         view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
-    ) -> LayoutId {
-        let style = self.compute_style(Bounds::default(), element_state, cx);
-        style.apply_text_style(cx, |cx| {
-            self.with_element_id(cx, |this, _global_id, cx| {
-                let layout_ids = this
-                    .children
-                    .iter_mut()
-                    .map(|child| child.layout(view_state, cx))
-                    .collect::<SmallVec<_>>();
-                element_state.child_layout_ids = layout_ids.clone();
-                cx.request_layout(&style, layout_ids)
-            })
-        })
+    ) -> crate::LayoutId {
+        let mut interactivity = mem::take(&mut self.interactivity);
+        let layout_id =
+            interactivity.layout(&mut element_state.interactive_state, cx, |style, cx| {
+                cx.with_text_style(style.text_style().cloned(), |cx| {
+                    element_state.child_layout_ids = self
+                        .children
+                        .iter_mut()
+                        .map(|child| child.layout(view_state, cx))
+                        .collect::<SmallVec<_>>();
+                    cx.request_layout(&style, element_state.child_layout_ids.iter().copied())
+                })
+            });
+        self.interactivity = interactivity;
+        layout_id
     }
 
     fn paint(
@@ -272,117 +652,799 @@ where
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) {
-        self.with_element_id(cx, |this, _global_id, cx| {
-            let style = this.compute_style(bounds, element_state, cx);
-            if style.visibility == Visibility::Hidden {
-                return;
+        let mut child_min = point(Pixels::MAX, Pixels::MAX);
+        let mut child_max = Point::default();
+        let content_size = if element_state.child_layout_ids.is_empty() {
+            bounds.size
+        } else {
+            for child_layout_id in &element_state.child_layout_ids {
+                let child_bounds = cx.layout_bounds(*child_layout_id);
+                child_min = child_min.min(&child_bounds.origin);
+                child_max = child_max.max(&child_bounds.lower_right());
             }
+            (child_max - child_min).into()
+        };
 
-            if let Some(mouse_cursor) = style.mouse_cursor {
-                let hovered = bounds.contains_point(&cx.mouse_position());
-                if hovered {
-                    cx.set_cursor_style(mouse_cursor);
+        let mut interactivity = mem::take(&mut self.interactivity);
+        interactivity.paint(
+            bounds,
+            content_size,
+            &mut element_state.interactive_state,
+            cx,
+            |style, scroll_offset, cx| {
+                if style.visibility == Visibility::Hidden {
+                    return;
                 }
-            }
 
-            if let Some(group) = this.group.clone() {
-                GroupBounds::push(group, bounds, cx);
+                let z_index = style.z_index.unwrap_or(0);
+
+                cx.with_z_index(z_index, |cx| {
+                    cx.with_z_index(0, |cx| {
+                        style.paint(bounds, cx);
+                    });
+                    cx.with_z_index(1, |cx| {
+                        cx.with_text_style(style.text_style().cloned(), |cx| {
+                            cx.with_content_mask(style.overflow_mask(bounds), |cx| {
+                                cx.with_element_offset(scroll_offset, |cx| {
+                                    for child in &mut self.children {
+                                        child.paint(view_state, cx);
+                                    }
+                                })
+                            })
+                        })
+                    })
+                })
+            },
+        );
+        self.interactivity = interactivity;
+    }
+}
+
+impl<V: 'static> Component<V> for Div<V> {
+    fn render(self) -> AnyElement<V> {
+        AnyElement::new(self)
+    }
+}
+
+pub struct NodeState {
+    child_layout_ids: SmallVec<[LayoutId; 4]>,
+    interactive_state: InteractiveElementState,
+}
+
+pub struct Interactivity<V> {
+    pub element_id: Option<ElementId>,
+    pub key_context: KeyContext,
+    pub focusable: bool,
+    pub tracked_focus_handle: Option<FocusHandle>,
+    pub focus_listeners: FocusListeners<V>,
+    pub group: Option<SharedString>,
+    pub base_style: StyleRefinement,
+    pub focus_style: StyleRefinement,
+    pub focus_in_style: StyleRefinement,
+    pub in_focus_style: StyleRefinement,
+    pub hover_style: StyleRefinement,
+    pub group_hover_style: Option<GroupStyle>,
+    pub active_style: StyleRefinement,
+    pub group_active_style: Option<GroupStyle>,
+    pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
+    pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
+    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
+    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
+    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
+    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
+    pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
+    pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
+    pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
+    pub drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
+    pub click_listeners: SmallVec<[ClickListener<V>; 2]>,
+    pub drag_listener: Option<DragListener<V>>,
+    pub hover_listener: Option<HoverListener<V>>,
+    pub tooltip_builder: Option<TooltipBuilder<V>>,
+}
+
+impl<V> Interactivity<V>
+where
+    V: 'static,
+{
+    pub fn initialize(
+        &mut self,
+        element_state: Option<InteractiveElementState>,
+        cx: &mut ViewContext<V>,
+    ) -> InteractiveElementState {
+        let mut element_state = element_state.unwrap_or_default();
+
+        // Ensure we store a focus handle in our element state if we're focusable.
+        // If there's an explicit focus handle we're tracking, use that. Otherwise
+        // create a new handle and store it in the element state, which lives for as
+        // as frames contain an element with this id.
+        if self.focusable {
+            element_state.focus_handle.get_or_insert_with(|| {
+                self.tracked_focus_handle
+                    .clone()
+                    .unwrap_or_else(|| cx.focus_handle())
+            });
+        }
+
+        element_state
+    }
+
+    pub fn layout(
+        &mut self,
+        element_state: &mut InteractiveElementState,
+        cx: &mut ViewContext<V>,
+        f: impl FnOnce(Style, &mut ViewContext<V>) -> LayoutId,
+    ) -> LayoutId {
+        let style = self.compute_style(None, element_state, cx);
+        f(style, cx)
+    }
+
+    pub fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        content_size: Size<Pixels>,
+        element_state: &mut InteractiveElementState,
+        cx: &mut ViewContext<V>,
+        f: impl FnOnce(Style, Point<Pixels>, &mut ViewContext<V>),
+    ) {
+        let style = self.compute_style(Some(bounds), element_state, cx);
+
+        if let Some(mouse_cursor) = style.mouse_cursor {
+            let hovered = bounds.contains_point(&cx.mouse_position());
+            if hovered {
+                cx.set_cursor_style(mouse_cursor);
             }
+        }
+
+        for listener in self.mouse_down_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.mouse_up_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.mouse_move_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
 
-            let z_index = style.z_index.unwrap_or(0);
+        for listener in self.scroll_wheel_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
 
-            let mut child_min = point(Pixels::MAX, Pixels::MAX);
-            let mut child_max = Point::default();
+        let hover_group_bounds = self
+            .group_hover_style
+            .as_ref()
+            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
 
-            let content_size = if element_state.child_layout_ids.is_empty() {
-                bounds.size
-            } else {
-                for child_layout_id in &element_state.child_layout_ids {
-                    let child_bounds = cx.layout_bounds(*child_layout_id);
-                    child_min = child_min.min(&child_bounds.origin);
-                    child_max = child_max.max(&child_bounds.lower_right());
-                }
-                (child_max - child_min).into()
-            };
-
-            cx.with_z_index(z_index, |cx| {
-                cx.with_z_index(0, |cx| {
-                    style.paint(bounds, cx);
-                    this.key_dispatch.paint(bounds, cx);
-                    this.interactivity.paint(
-                        bounds,
-                        content_size,
-                        style.overflow,
-                        &mut element_state.interactive,
-                        cx,
-                    );
+        if let Some(group_bounds) = hover_group_bounds {
+            let hovered = group_bounds.contains_point(&cx.mouse_position());
+            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    if group_bounds.contains_point(&event.position) != hovered {
+                        cx.notify();
+                    }
+                }
+            });
+        }
+
+        if self.hover_style.is_some()
+            || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
+        {
+            let hovered = bounds.contains_point(&cx.mouse_position());
+            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    if bounds.contains_point(&event.position) != hovered {
+                        cx.notify();
+                    }
+                }
+            });
+        }
+
+        if cx.active_drag.is_some() {
+            let drop_listeners = mem::take(&mut self.drop_listeners);
+            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    if let Some(drag_state_type) =
+                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
+                    {
+                        for (drop_state_type, listener) in &drop_listeners {
+                            if *drop_state_type == drag_state_type {
+                                let drag = cx
+                                    .active_drag
+                                    .take()
+                                    .expect("checked for type drag state type above");
+                                listener(view, drag.view.clone(), cx);
+                                cx.notify();
+                                cx.stop_propagation();
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        let click_listeners = mem::take(&mut self.click_listeners);
+        let drag_listener = mem::take(&mut self.drag_listener);
+
+        if !click_listeners.is_empty() || drag_listener.is_some() {
+            let pending_mouse_down = element_state.pending_mouse_down.clone();
+            let mouse_down = pending_mouse_down.lock().clone();
+            if let Some(mouse_down) = mouse_down {
+                if let Some(drag_listener) = drag_listener {
+                    let active_state = element_state.clicked_state.clone();
+
+                    cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+                        if cx.active_drag.is_some() {
+                            if phase == DispatchPhase::Capture {
+                                cx.notify();
+                            }
+                        } else if phase == DispatchPhase::Bubble
+                            && bounds.contains_point(&event.position)
+                            && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD
+                        {
+                            *active_state.lock() = ElementClickedState::default();
+                            let cursor_offset = event.position - bounds.origin;
+                            let drag = drag_listener(view_state, cursor_offset, cx);
+                            cx.active_drag = Some(drag);
+                            cx.notify();
+                            cx.stop_propagation();
+                        }
+                    });
+                }
+
+                cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                        let mouse_click = ClickEvent {
+                            down: mouse_down.clone(),
+                            up: event.clone(),
+                        };
+                        for listener in &click_listeners {
+                            listener(view_state, &mouse_click, cx);
+                        }
+                    }
+                    *pending_mouse_down.lock() = None;
                 });
-                cx.with_z_index(1, |cx| {
-                    style.apply_text_style(cx, |cx| {
-                        style.apply_overflow(bounds, cx, |cx| {
-                            let scroll_offset = element_state.interactive.scroll_offset();
-                            cx.with_element_offset(scroll_offset, |cx| {
-                                for child in &mut this.children {
-                                    child.paint(view_state, cx);
-                                }
-                            });
-                        })
-                    })
+            } else {
+                cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
+                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                        *pending_mouse_down.lock() = Some(event.clone());
+                    }
                 });
+            }
+        }
+
+        if let Some(hover_listener) = self.hover_listener.take() {
+            let was_hovered = element_state.hover_state.clone();
+            let has_mouse_down = element_state.pending_mouse_down.clone();
+
+            cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+                let is_hovered =
+                    bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
+                let mut was_hovered = was_hovered.lock();
+
+                if is_hovered != was_hovered.clone() {
+                    *was_hovered = is_hovered;
+                    drop(was_hovered);
+
+                    hover_listener(view_state, is_hovered, cx);
+                }
+            });
+        }
+
+        if let Some(tooltip_builder) = self.tooltip_builder.take() {
+            let active_tooltip = element_state.active_tooltip.clone();
+            let pending_mouse_down = element_state.pending_mouse_down.clone();
+
+            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                if phase != DispatchPhase::Bubble {
+                    return;
+                }
+
+                let is_hovered =
+                    bounds.contains_point(&event.position) && pending_mouse_down.lock().is_none();
+                if !is_hovered {
+                    active_tooltip.lock().take();
+                    return;
+                }
+
+                if active_tooltip.lock().is_none() {
+                    let task = cx.spawn({
+                        let active_tooltip = active_tooltip.clone();
+                        let tooltip_builder = tooltip_builder.clone();
+
+                        move |view, mut cx| async move {
+                            cx.background_executor().timer(TOOLTIP_DELAY).await;
+                            view.update(&mut cx, move |view_state, cx| {
+                                active_tooltip.lock().replace(ActiveTooltip {
+                                    waiting: None,
+                                    tooltip: Some(AnyTooltip {
+                                        view: tooltip_builder(view_state, cx),
+                                        cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
+                                    }),
+                                });
+                                cx.notify();
+                            })
+                            .ok();
+                        }
+                    });
+                    active_tooltip.lock().replace(ActiveTooltip {
+                        waiting: Some(task),
+                        tooltip: None,
+                    });
+                }
+            });
+
+            if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
+                if active_tooltip.tooltip.is_some() {
+                    cx.active_tooltip = active_tooltip.tooltip.clone()
+                }
+            }
+        }
+
+        let active_state = element_state.clicked_state.clone();
+        if !active_state.lock().is_clicked() {
+            cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    *active_state.lock() = ElementClickedState::default();
+                    cx.notify();
+                }
+            });
+        } else {
+            let active_group_bounds = self
+                .group_active_style
+                .as_ref()
+                .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
+            cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    let group = active_group_bounds
+                        .map_or(false, |bounds| bounds.contains_point(&down.position));
+                    let element = bounds.contains_point(&down.position);
+                    if group || element {
+                        *active_state.lock() = ElementClickedState { group, element };
+                        cx.notify();
+                    }
+                }
+            });
+        }
+
+        let overflow = style.overflow;
+        if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
+            let scroll_offset = element_state
+                .scroll_offset
+                .get_or_insert_with(Arc::default)
+                .clone();
+            let line_height = cx.line_height();
+            let scroll_max = (content_size - bounds.size).max(&Size::default());
+
+            cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    let mut scroll_offset = scroll_offset.lock();
+                    let old_scroll_offset = *scroll_offset;
+                    let delta = event.delta.pixel_delta(line_height);
+
+                    if overflow.x == Overflow::Scroll {
+                        scroll_offset.x =
+                            (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
+                    }
+
+                    if overflow.y == Overflow::Scroll {
+                        scroll_offset.y =
+                            (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
+                    }
+
+                    if *scroll_offset != old_scroll_offset {
+                        cx.notify();
+                        cx.stop_propagation();
+                    }
+                }
             });
+        }
+
+        if let Some(group) = self.group.clone() {
+            GroupBounds::push(group, bounds, cx);
+        }
+
+        let scroll_offset = element_state
+            .scroll_offset
+            .as_ref()
+            .map(|scroll_offset| *scroll_offset.lock());
+
+        cx.with_key_dispatch(
+            self.key_context.clone(),
+            element_state.focus_handle.clone(),
+            |_, cx| {
+                for listener in self.key_down_listeners.drain(..) {
+                    cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
+                        listener(state, event, phase, cx);
+                    })
+                }
 
-            if let Some(group) = this.group.as_ref() {
-                GroupBounds::pop(group, cx);
+                for listener in self.key_up_listeners.drain(..) {
+                    cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
+                        listener(state, event, phase, cx);
+                    })
+                }
+
+                for (action_type, listener) in self.action_listeners.drain(..) {
+                    cx.on_action(action_type, listener)
+                }
+
+                if let Some(focus_handle) = element_state.focus_handle.as_ref() {
+                    for listener in self.focus_listeners.drain(..) {
+                        let focus_handle = focus_handle.clone();
+                        cx.on_focus_changed(move |view, event, cx| {
+                            listener(view, &focus_handle, event, cx)
+                        });
+                    }
+                }
+
+                f(style, scroll_offset.unwrap_or_default(), cx)
+            },
+        );
+
+        if let Some(group) = self.group.as_ref() {
+            GroupBounds::pop(group, cx);
+        }
+    }
+
+    pub fn compute_style(
+        &self,
+        bounds: Option<Bounds<Pixels>>,
+        element_state: &mut InteractiveElementState,
+        cx: &mut ViewContext<V>,
+    ) -> Style {
+        let mut style = Style::default();
+        style.refine(&self.base_style);
+
+        if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
+            if focus_handle.contains_focused(cx) {
+                style.refine(&self.focus_in_style);
+            }
+
+            if focus_handle.within_focused(cx) {
+                style.refine(&self.in_focus_style);
+            }
+
+            if focus_handle.is_focused(cx) {
+                style.refine(&self.focus_style);
+            }
+        }
+
+        if let Some(bounds) = bounds {
+            let mouse_position = cx.mouse_position();
+            if let Some(group_hover) = self.group_hover_style.as_ref() {
+                if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
+                    if group_bounds.contains_point(&mouse_position) {
+                        style.refine(&group_hover.style);
+                    }
+                }
+            }
+            if bounds.contains_point(&mouse_position) {
+                style.refine(&self.hover_style);
             }
-        })
+
+            if let Some(drag) = cx.active_drag.take() {
+                for (state_type, group_drag_style) in &self.group_drag_over_styles {
+                    if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
+                        if *state_type == drag.view.entity_type()
+                            && group_bounds.contains_point(&mouse_position)
+                        {
+                            style.refine(&group_drag_style.style);
+                        }
+                    }
+                }
+
+                for (state_type, drag_over_style) in &self.drag_over_styles {
+                    if *state_type == drag.view.entity_type()
+                        && bounds.contains_point(&mouse_position)
+                    {
+                        style.refine(drag_over_style);
+                    }
+                }
+
+                cx.active_drag = Some(drag);
+            }
+        }
+
+        let clicked_state = element_state.clicked_state.lock();
+        if clicked_state.group {
+            if let Some(group) = self.group_active_style.as_ref() {
+                style.refine(&group.style)
+            }
+        }
+
+        if clicked_state.element {
+            style.refine(&self.active_style)
+        }
+
+        style
+    }
+}
+
+impl<V: 'static> Default for Interactivity<V> {
+    fn default() -> Self {
+        Self {
+            element_id: None,
+            key_context: KeyContext::default(),
+            focusable: false,
+            tracked_focus_handle: None,
+            focus_listeners: SmallVec::default(),
+            // scroll_offset: Point::default(),
+            group: None,
+            base_style: StyleRefinement::default(),
+            focus_style: StyleRefinement::default(),
+            focus_in_style: StyleRefinement::default(),
+            in_focus_style: StyleRefinement::default(),
+            hover_style: StyleRefinement::default(),
+            group_hover_style: None,
+            active_style: StyleRefinement::default(),
+            group_active_style: None,
+            drag_over_styles: SmallVec::new(),
+            group_drag_over_styles: SmallVec::new(),
+            mouse_down_listeners: SmallVec::new(),
+            mouse_up_listeners: SmallVec::new(),
+            mouse_move_listeners: SmallVec::new(),
+            scroll_wheel_listeners: SmallVec::new(),
+            key_down_listeners: SmallVec::new(),
+            key_up_listeners: SmallVec::new(),
+            action_listeners: SmallVec::new(),
+            drop_listeners: SmallVec::new(),
+            click_listeners: SmallVec::new(),
+            drag_listener: None,
+            hover_listener: None,
+            tooltip_builder: None,
+        }
     }
 }
 
-impl<V, I, F> Component<V> for Div<V, I, F>
+#[derive(Default)]
+pub struct InteractiveElementState {
+    pub focus_handle: Option<FocusHandle>,
+    pub clicked_state: Arc<Mutex<ElementClickedState>>,
+    pub hover_state: Arc<Mutex<bool>>,
+    pub pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
+    pub scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
+    pub active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
+}
+
+pub struct ActiveTooltip {
+    #[allow(unused)] // used to drop the task
+    waiting: Option<Task<()>>,
+    tooltip: Option<AnyTooltip>,
+}
+
+/// Whether or not the element or a group that contains it is clicked by the mouse.
+#[derive(Copy, Clone, Default, Eq, PartialEq)]
+pub struct ElementClickedState {
+    pub group: bool,
+    pub element: bool,
+}
+
+impl ElementClickedState {
+    fn is_clicked(&self) -> bool {
+        self.group || self.element
+    }
+}
+
+#[derive(Default)]
+pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
+
+impl GroupBounds {
+    pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
+        cx.default_global::<Self>()
+            .0
+            .get(name)
+            .and_then(|bounds_stack| bounds_stack.last())
+            .cloned()
+    }
+
+    pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
+        cx.default_global::<Self>()
+            .0
+            .entry(name)
+            .or_default()
+            .push(bounds);
+    }
+
+    pub fn pop(name: &SharedString, cx: &mut AppContext) {
+        cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
+    }
+}
+
+pub struct Focusable<V, E> {
+    element: E,
+    view_type: PhantomData<V>,
+}
+
+impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {}
+
+impl<V, E> InteractiveComponent<V> for Focusable<V, E>
+where
+    V: 'static,
+    E: InteractiveComponent<V>,
+{
+    fn interactivity(&mut self) -> &mut Interactivity<V> {
+        self.element.interactivity()
+    }
+}
+
+impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E>
+    for Focusable<V, E>
+{
+}
+
+impl<V, E> Styled for Focusable<V, E>
+where
+    V: 'static,
+    E: Styled,
+{
+    fn style(&mut self) -> &mut StyleRefinement {
+        self.element.style()
+    }
+}
+
+impl<V, E> Element<V> for Focusable<V, E>
+where
+    V: 'static,
+    E: Element<V>,
+{
+    type ElementState = E::ElementState;
+
+    fn element_id(&self) -> Option<ElementId> {
+        self.element.element_id()
+    }
+
+    fn initialize(
+        &mut self,
+        view_state: &mut V,
+        element_state: Option<Self::ElementState>,
+        cx: &mut ViewContext<V>,
+    ) -> Self::ElementState {
+        self.element.initialize(view_state, element_state, cx)
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) -> LayoutId {
+        self.element.layout(view_state, element_state, cx)
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        self.element.paint(bounds, view_state, element_state, cx);
+    }
+}
+
+impl<V, E> Component<V> for Focusable<V, E>
 where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
+    V: 'static,
+    E: 'static + Element<V>,
 {
     fn render(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<V, I, F> ParentElement<V> for Div<V, I, F>
+impl<V, E> ParentComponent<V> for Focusable<V, E>
 where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
+    V: 'static,
+    E: ParentComponent<V>,
 {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
+        self.element.children_mut()
     }
 }
 
-impl<V, I, F> Styled for Div<V, I, F>
+pub struct Stateful<V, E> {
+    element: E,
+    view_type: PhantomData<V>,
+}
+
+impl<V, E> Styled for Stateful<V, E>
 where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
+    V: 'static,
+    E: Styled,
 {
     fn style(&mut self) -> &mut StyleRefinement {
-        &mut self.base_style
+        self.element.style()
     }
 }
 
-impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
+impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E>
+where
+    V: 'static,
+    E: Element<V>,
+    Self: InteractiveComponent<V>,
+{
+}
+
+impl<V, E> InteractiveComponent<V> for Stateful<V, E>
 where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
+    V: 'static,
+    E: InteractiveComponent<V>,
 {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
-        self.interactivity.as_stateless_mut()
+    fn interactivity(&mut self) -> &mut Interactivity<V> {
+        self.element.interactivity()
     }
 }
 
-impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
+impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {}
+
+impl<V, E> Element<V> for Stateful<V, E>
 where
-    F: KeyDispatch<V>,
+    V: 'static,
+    E: Element<V>,
 {
-    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
-        &mut self.interactivity
+    type ElementState = E::ElementState;
+
+    fn element_id(&self) -> Option<ElementId> {
+        self.element.element_id()
+    }
+
+    fn initialize(
+        &mut self,
+        view_state: &mut V,
+        element_state: Option<Self::ElementState>,
+        cx: &mut ViewContext<V>,
+    ) -> Self::ElementState {
+        self.element.initialize(view_state, element_state, cx)
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) -> LayoutId {
+        self.element.layout(view_state, element_state, cx)
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        self.element.paint(bounds, view_state, element_state, cx)
+    }
+}
+
+impl<V, E> Component<V> for Stateful<V, E>
+where
+    V: 'static,
+    E: 'static + Element<V>,
+{
+    fn render(self) -> AnyElement<V> {
+        AnyElement::new(self)
+    }
+}
+
+impl<V, E> ParentComponent<V> for Stateful<V, E>
+where
+    V: 'static,
+    E: ParentComponent<V>,
+{
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        self.element.children_mut()
     }
 }

crates/gpui2/src/elements/img.rs 🔗

@@ -1,35 +1,28 @@
 use crate::{
-    div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementId,
-    ElementInteractivity, FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
-    NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
-    StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
+    AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent,
+    InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
+    Styled, ViewContext,
 };
 use futures::FutureExt;
 use util::ResultExt;
 
-pub struct Img<
-    V: 'static,
-    I: ElementInteractivity<V> = StatelessInteractivity<V>,
-    F: KeyDispatch<V> = NonFocusableKeyDispatch,
-> {
-    base: Div<V, I, F>,
+pub struct Img<V: 'static> {
+    interactivity: Interactivity<V>,
     uri: Option<SharedString>,
     grayscale: bool,
 }
 
-pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
+pub fn img<V: 'static>() -> Img<V> {
     Img {
-        base: div(),
+        interactivity: Interactivity::default(),
         uri: None,
         grayscale: false,
     }
 }
 
-impl<V, I, F> Img<V, I, F>
+impl<V> Img<V>
 where
     V: 'static,
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
 {
     pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
         self.uri = Some(uri.into());
@@ -42,145 +35,90 @@ where
     }
 }
 
-impl<V, F> Img<V, StatelessInteractivity<V>, F>
-where
-    F: KeyDispatch<V>,
-{
-    pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
-        Img {
-            base: self.base.id(id),
-            uri: self.uri,
-            grayscale: self.grayscale,
-        }
-    }
-}
-
-impl<V, I, F> Component<V> for Img<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
+impl<V> Component<V> for Img<V> {
     fn render(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<V, I, F> Element<V> for Img<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    type ElementState = DivState;
+impl<V> Element<V> for Img<V> {
+    type ElementState = InteractiveElementState;
 
-    fn id(&self) -> Option<crate::ElementId> {
-        self.base.id()
+    fn element_id(&self) -> Option<crate::ElementId> {
+        self.interactivity.element_id.clone()
     }
 
     fn initialize(
         &mut self,
-        view_state: &mut V,
+        _view_state: &mut V,
         element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<V>,
     ) -> Self::ElementState {
-        self.base.initialize(view_state, element_state, cx)
+        self.interactivity.initialize(element_state, cx)
     }
 
     fn layout(
         &mut self,
-        view_state: &mut V,
+        _view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) -> LayoutId {
-        self.base.layout(view_state, element_state, cx)
+        self.interactivity.layout(element_state, cx, |style, cx| {
+            cx.request_layout(&style, None)
+        })
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        view: &mut V,
+        _view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) {
-        cx.with_z_index(0, |cx| {
-            self.base.paint(bounds, view, element_state, cx);
-        });
-
-        let style = self.base.compute_style(bounds, element_state, cx);
-        let corner_radii = style.corner_radii;
-
-        if let Some(uri) = self.uri.clone() {
-            // eprintln!(">>> image_cache.get({uri}");
-            let image_future = cx.image_cache.get(uri.clone());
-            // eprintln!("<<< image_cache.get({uri}");
-            if let Some(data) = image_future
-                .clone()
-                .now_or_never()
-                .and_then(ResultExt::log_err)
-            {
-                let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
-                cx.with_z_index(1, |cx| {
-                    cx.paint_image(bounds, corner_radii, data, self.grayscale)
-                        .log_err()
-                });
-            } else {
-                cx.spawn(|_, mut cx| async move {
-                    if image_future.await.log_err().is_some() {
-                        cx.on_next_frame(|cx| cx.notify());
+        self.interactivity.paint(
+            bounds,
+            bounds.size,
+            element_state,
+            cx,
+            |style, _scroll_offset, cx| {
+                let corner_radii = style.corner_radii;
+
+                if let Some(uri) = self.uri.clone() {
+                    // eprintln!(">>> image_cache.get({uri}");
+                    let image_future = cx.image_cache.get(uri.clone());
+                    // eprintln!("<<< image_cache.get({uri}");
+                    if let Some(data) = image_future
+                        .clone()
+                        .now_or_never()
+                        .and_then(ResultExt::log_err)
+                    {
+                        let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
+                        cx.with_z_index(1, |cx| {
+                            cx.paint_image(bounds, corner_radii, data, self.grayscale)
+                                .log_err()
+                        });
+                    } else {
+                        cx.spawn(|_, mut cx| async move {
+                            if image_future.await.log_err().is_some() {
+                                cx.on_next_frame(|cx| cx.notify());
+                            }
+                        })
+                        .detach()
                     }
-                })
-                .detach()
-            }
-        }
+                }
+            },
+        )
     }
 }
 
-impl<V, I, F> Styled for Img<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
+impl<V> Styled for Img<V> {
     fn style(&mut self) -> &mut StyleRefinement {
-        self.base.style()
+        &mut self.interactivity.base_style
     }
 }
 
-impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
-        self.base.stateless_interactivity()
-    }
-}
-
-impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
-where
-    F: KeyDispatch<V>,
-{
-    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
-        self.base.stateful_interactivity()
-    }
-}
-
-impl<V, I> Focusable<V> for Img<V, I, FocusableKeyDispatch<V>>
-where
-    V: 'static,
-    I: ElementInteractivity<V>,
-{
-    fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
-        self.base.focus_listeners()
-    }
-
-    fn set_focus_style(&mut self, style: StyleRefinement) {
-        self.base.set_focus_style(style)
-    }
-
-    fn set_focus_in_style(&mut self, style: StyleRefinement) {
-        self.base.set_focus_in_style(style)
-    }
-
-    fn set_in_focus_style(&mut self, style: StyleRefinement) {
-        self.base.set_in_focus_style(style)
+impl<V> InteractiveComponent<V> for Img<V> {
+    fn interactivity(&mut self) -> &mut Interactivity<V> {
+        &mut self.interactivity
     }
 }

crates/gpui2/src/elements/svg.rs 🔗

@@ -1,157 +1,88 @@
 use crate::{
-    div, AnyElement, Bounds, Component, Div, DivState, Element, ElementId, ElementInteractivity,
-    FocusListeners, Focusable, FocusableKeyDispatch, KeyDispatch, LayoutId,
-    NonFocusableKeyDispatch, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
-    StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
+    AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent,
+    InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
+    Styled, ViewContext,
 };
 use util::ResultExt;
 
-pub struct Svg<
-    V: 'static,
-    I: ElementInteractivity<V> = StatelessInteractivity<V>,
-    F: KeyDispatch<V> = NonFocusableKeyDispatch,
-> {
-    base: Div<V, I, F>,
+pub struct Svg<V: 'static> {
+    interactivity: Interactivity<V>,
     path: Option<SharedString>,
 }
 
-pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, NonFocusableKeyDispatch> {
+pub fn svg<V: 'static>() -> Svg<V> {
     Svg {
-        base: div(),
+        interactivity: Interactivity::default(),
         path: None,
     }
 }
 
-impl<V, I, F> Svg<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
+impl<V> Svg<V> {
     pub fn path(mut self, path: impl Into<SharedString>) -> Self {
         self.path = Some(path.into());
         self
     }
 }
 
-impl<V, F> Svg<V, StatelessInteractivity<V>, F>
-where
-    F: KeyDispatch<V>,
-{
-    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
-        Svg {
-            base: self.base.id(id),
-            path: self.path,
-        }
-    }
-}
-
-impl<V, I, F> Component<V> for Svg<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
+impl<V> Component<V> for Svg<V> {
     fn render(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<V, I, F> Element<V> for Svg<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    type ElementState = DivState;
+impl<V> Element<V> for Svg<V> {
+    type ElementState = InteractiveElementState;
 
-    fn id(&self) -> Option<crate::ElementId> {
-        self.base.id()
+    fn element_id(&self) -> Option<ElementId> {
+        self.interactivity.element_id.clone()
     }
 
     fn initialize(
         &mut self,
-        view_state: &mut V,
+        _view_state: &mut V,
         element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<V>,
     ) -> Self::ElementState {
-        self.base.initialize(view_state, element_state, cx)
+        self.interactivity.initialize(element_state, cx)
     }
 
     fn layout(
         &mut self,
-        view_state: &mut V,
+        _view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) -> LayoutId {
-        self.base.layout(view_state, element_state, cx)
+        self.interactivity.layout(element_state, cx, |style, cx| {
+            cx.request_layout(&style, None)
+        })
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        view: &mut V,
+        _view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
     {
-        self.base.paint(bounds, view, element_state, cx);
-        let color = self
-            .base
-            .compute_style(bounds, element_state, cx)
-            .text
-            .color;
-        if let Some((path, color)) = self.path.as_ref().zip(color) {
-            cx.paint_svg(bounds, path.clone(), color).log_err();
-        }
+        self.interactivity
+            .paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
+                if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
+                    cx.paint_svg(bounds, path.clone(), color).log_err();
+                }
+            })
     }
 }
 
-impl<V, I, F> Styled for Svg<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
+impl<V> Styled for Svg<V> {
     fn style(&mut self) -> &mut StyleRefinement {
-        self.base.style()
-    }
-}
-
-impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
-        self.base.stateless_interactivity()
+        &mut self.interactivity.base_style
     }
 }
 
-impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
-where
-    V: 'static,
-    F: KeyDispatch<V>,
-{
-    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
-        self.base.stateful_interactivity()
-    }
-}
-
-impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusableKeyDispatch<V>>
-where
-    I: ElementInteractivity<V>,
-{
-    fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
-        self.base.focus_listeners()
-    }
-
-    fn set_focus_style(&mut self, style: StyleRefinement) {
-        self.base.set_focus_style(style)
-    }
-
-    fn set_focus_in_style(&mut self, style: StyleRefinement) {
-        self.base.set_focus_in_style(style)
-    }
-
-    fn set_in_focus_style(&mut self, style: StyleRefinement) {
-        self.base.set_in_focus_style(style)
+impl<V> InteractiveComponent<V> for Svg<V> {
+    fn interactivity(&mut self) -> &mut Interactivity<V> {
+        &mut self.interactivity
     }
 }

crates/gpui2/src/elements/text.rs 🔗

@@ -72,7 +72,7 @@ impl<V: 'static> Component<V> for Text<V> {
 impl<V: 'static> Element<V> for Text<V> {
     type ElementState = Arc<Mutex<Option<TextElementState>>>;
 
-    fn id(&self) -> Option<crate::ElementId> {
+    fn element_id(&self) -> Option<crate::ElementId> {
         None
     }
 

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -1,24 +1,23 @@
 use crate::{
     point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,
-    ElementId, ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
-    StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
-    StyleRefinement, Styled, ViewContext,
+    ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels,
+    Point, Size, StyleRefinement, Styled, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
-use std::{cmp, ops::Range, sync::Arc};
+use std::{cmp, mem, ops::Range, sync::Arc};
 use taffy::style::Overflow;
 
 /// uniform_list provides lazy rendering for a set of items that are of uniform height.
 /// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
 /// uniform_list will only render the visibile subset of items.
-pub fn uniform_list<Id, V, C>(
-    id: Id,
+pub fn uniform_list<I, V, C>(
+    id: I,
     item_count: usize,
     f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<C>,
 ) -> UniformList<V>
 where
-    Id: Into<ElementId>,
+    I: Into<ElementId>,
     V: 'static,
     C: Component<V>,
 {
@@ -37,7 +36,10 @@ where
                 .map(|component| component.render())
                 .collect()
         }),
-        interactivity: StatefulInteractivity::new(id, StatelessInteractivity::default()),
+        interactivity: Interactivity {
+            element_id: Some(id.into()),
+            ..Default::default()
+        },
         scroll_handle: None,
     }
 }
@@ -54,7 +56,7 @@ pub struct UniformList<V: 'static> {
             &'a mut ViewContext<V>,
         ) -> SmallVec<[AnyElement<V>; 64]>,
     >,
-    interactivity: StatefulInteractivity<V>,
+    interactivity: Interactivity<V>,
     scroll_handle: Option<UniformListScrollHandle>,
 }
 
@@ -103,7 +105,7 @@ pub struct UniformListState {
 impl<V: 'static> Element<V> for UniformList<V> {
     type ElementState = UniformListState;
 
-    fn id(&self) -> Option<crate::ElementId> {
+    fn element_id(&self) -> Option<crate::ElementId> {
         Some(self.id.clone())
     }
 
@@ -113,13 +115,18 @@ impl<V: 'static> Element<V> for UniformList<V> {
         element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<V>,
     ) -> Self::ElementState {
-        element_state.unwrap_or_else(|| {
+        if let Some(mut element_state) = element_state {
+            element_state.interactive = self
+                .interactivity
+                .initialize(Some(element_state.interactive), cx);
+            element_state
+        } else {
             let item_size = self.measure_item(view_state, None, cx);
             UniformListState {
-                interactive: InteractiveElementState::default(),
+                interactive: self.interactivity.initialize(None, cx),
                 item_size,
             }
-        })
+        }
     }
 
     fn layout(
@@ -132,35 +139,44 @@ impl<V: 'static> Element<V> for UniformList<V> {
         let item_size = element_state.item_size;
         let rem_size = cx.rem_size();
 
-        cx.request_measured_layout(
-            self.computed_style(),
-            rem_size,
-            move |known_dimensions: Size<Option<Pixels>>, available_space: Size<AvailableSpace>| {
-                let desired_height = item_size.height * max_items;
-                let width = known_dimensions
-                    .width
-                    .unwrap_or(match available_space.width {
-                        AvailableSpace::Definite(x) => x,
-                        AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
-                    });
-                let height = match available_space.height {
-                    AvailableSpace::Definite(x) => desired_height.min(x),
-                    AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
-                };
-                size(width, height)
-            },
-        )
+        self.interactivity
+            .layout(&mut element_state.interactive, cx, |style, cx| {
+                cx.request_measured_layout(
+                    style,
+                    rem_size,
+                    move |known_dimensions: Size<Option<Pixels>>,
+                          available_space: Size<AvailableSpace>| {
+                        let desired_height = item_size.height * max_items;
+                        let width = known_dimensions
+                            .width
+                            .unwrap_or(match available_space.width {
+                                AvailableSpace::Definite(x) => x,
+                                AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+                                    item_size.width
+                                }
+                            });
+                        let height = match available_space.height {
+                            AvailableSpace::Definite(x) => desired_height.min(x),
+                            AvailableSpace::MinContent | AvailableSpace::MaxContent => {
+                                desired_height
+                            }
+                        };
+                        size(width, height)
+                    },
+                )
+            })
     }
 
     fn paint(
         &mut self,
-        bounds: crate::Bounds<crate::Pixels>,
+        bounds: Bounds<crate::Pixels>,
         view_state: &mut V,
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<V>,
     ) {
-        let style = self.computed_style();
-
+        let style =
+            self.interactivity
+                .compute_style(Some(bounds), &mut element_state.interactive, cx);
         let border = style.border_widths.to_pixels(cx.rem_size());
         let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
 
@@ -170,74 +186,79 @@ impl<V: 'static> Element<V> for UniformList<V> {
                 - point(border.right + padding.right, border.bottom + padding.bottom),
         );
 
-        cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
-            style.paint(bounds, cx);
+        let item_size = element_state.item_size;
+        let content_size = Size {
+            width: padded_bounds.size.width,
+            height: item_size.height * self.item_count,
+        };
 
-            let content_size;
-            if self.item_count > 0 {
-                let item_height = self
-                    .measure_item(view_state, Some(padded_bounds.size.width), cx)
-                    .height;
-                if let Some(scroll_handle) = self.scroll_handle.clone() {
-                    scroll_handle.0.lock().replace(ScrollHandleState {
-                        item_height,
-                        list_height: padded_bounds.size.height,
-                        scroll_offset: element_state.interactive.track_scroll_offset(),
-                    });
-                }
-                let visible_item_count = if item_height > px(0.) {
-                    (padded_bounds.size.height / item_height).ceil() as usize + 1
-                } else {
-                    0
-                };
-                let scroll_offset = element_state
-                    .interactive
-                    .scroll_offset()
-                    .map_or((0.0).into(), |offset| offset.y);
-                let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
-                let visible_range = first_visible_element_ix
-                    ..cmp::min(
-                        first_visible_element_ix + visible_item_count,
-                        self.item_count,
-                    );
+        let mut interactivity = mem::take(&mut self.interactivity);
+        let shared_scroll_offset = element_state
+            .interactive
+            .scroll_offset
+            .get_or_insert_with(Arc::default)
+            .clone();
 
-                let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
+        interactivity.paint(
+            bounds,
+            content_size,
+            &mut element_state.interactive,
+            cx,
+            |style, scroll_offset, cx| {
+                let border = style.border_widths.to_pixels(cx.rem_size());
+                let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
 
-                content_size = Size {
-                    width: padded_bounds.size.width,
-                    height: item_height * self.item_count,
-                };
+                let padded_bounds = Bounds::from_corners(
+                    bounds.origin + point(border.left + padding.left, border.top + padding.top),
+                    bounds.lower_right()
+                        - point(border.right + padding.right, border.bottom + padding.bottom),
+                );
 
-                cx.with_z_index(1, |cx| {
-                    for (item, ix) in items.iter_mut().zip(visible_range) {
-                        let item_origin =
-                            padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
-                        let available_space = size(
-                            AvailableSpace::Definite(padded_bounds.size.width),
-                            AvailableSpace::Definite(item_height),
-                        );
-                        item.draw(item_origin, available_space, view_state, cx);
-                    }
-                });
-            } else {
-                content_size = Size {
-                    width: bounds.size.width,
-                    height: px(0.),
-                };
-            }
+                cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
+                    style.paint(bounds, cx);
 
-            let overflow = point(style.overflow.x, Overflow::Scroll);
+                    if self.item_count > 0 {
+                        let item_height = self
+                            .measure_item(view_state, Some(padded_bounds.size.width), cx)
+                            .height;
+                        if let Some(scroll_handle) = self.scroll_handle.clone() {
+                            scroll_handle.0.lock().replace(ScrollHandleState {
+                                item_height,
+                                list_height: padded_bounds.size.height,
+                                scroll_offset: shared_scroll_offset,
+                            });
+                        }
+                        let visible_item_count = if item_height > px(0.) {
+                            (padded_bounds.size.height / item_height).ceil() as usize + 1
+                        } else {
+                            0
+                        };
 
-            cx.with_z_index(0, |cx| {
-                self.interactivity.paint(
-                    bounds,
-                    content_size,
-                    overflow,
-                    &mut element_state.interactive,
-                    cx,
-                );
-            });
-        })
+                        let first_visible_element_ix =
+                            (-scroll_offset.y / item_height).floor() as usize;
+                        let visible_range = first_visible_element_ix
+                            ..cmp::min(
+                                first_visible_element_ix + visible_item_count,
+                                self.item_count,
+                            );
+
+                        let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
+                        cx.with_z_index(1, |cx| {
+                            for (item, ix) in items.iter_mut().zip(visible_range) {
+                                let item_origin = padded_bounds.origin
+                                    + point(px(0.), item_height * ix + scroll_offset.y);
+                                let available_space = size(
+                                    AvailableSpace::Definite(padded_bounds.size.width),
+                                    AvailableSpace::Definite(item_height),
+                                );
+                                item.draw(item_origin, available_space, view_state, cx);
+                            }
+                        });
+                    }
+                })
+            },
+        );
+        self.interactivity = interactivity;
     }
 }
 
@@ -275,14 +296,8 @@ impl<V> UniformList<V> {
     }
 }
 
-impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
-        self.interactivity.as_stateless_mut()
-    }
-}
-
-impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
-    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+impl<V> InteractiveComponent<V> for UniformList<V> {
+    fn interactivity(&mut self) -> &mut crate::Interactivity<V> {
         &mut self.interactivity
     }
 }

crates/gpui2/src/gpui2.rs 🔗

@@ -156,7 +156,7 @@ pub enum GlobalKey {
 }
 
 pub trait BorrowAppContext {
-    fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
+    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
     where
         F: FnOnce(&mut Self) -> R;
 
@@ -167,14 +167,18 @@ impl<C> BorrowAppContext for C
 where
     C: BorrowMut<AppContext>,
 {
-    fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
+    fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
     where
         F: FnOnce(&mut Self) -> R,
     {
-        self.borrow_mut().push_text_style(style);
-        let result = f(self);
-        self.borrow_mut().pop_text_style();
-        result
+        if let Some(style) = style {
+            self.borrow_mut().push_text_style(style);
+            let result = f(self);
+            self.borrow_mut().pop_text_style();
+            result
+        } else {
+            f(self)
+        }
     }
 
     fn set_global<G: 'static>(&mut self, global: G) {

crates/gpui2/src/interactive.rs 🔗

@@ -1,944 +1,9 @@
 use crate::{
-    div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Bounds, Component,
-    DispatchPhase, Div, Element, ElementId, FocusHandle, KeyContext, Keystroke, Modifiers,
-    Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, Task, View,
+    div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,
     ViewContext,
 };
-use collections::HashMap;
-use derive_more::{Deref, DerefMut};
-use parking_lot::Mutex;
-use refineable::Refineable;
 use smallvec::SmallVec;
-use std::{
-    any::{Any, TypeId},
-    fmt::Debug,
-    marker::PhantomData,
-    mem,
-    ops::Deref,
-    path::PathBuf,
-    sync::Arc,
-    time::Duration,
-};
-
-const DRAG_THRESHOLD: f64 = 2.;
-const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
-const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
-
-pub trait StatelessInteractive<V: 'static>: Element<V> {
-    fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
-
-    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().hover_style = f(StyleRefinement::default());
-        self
-    }
-
-    fn group_hover(
-        mut self,
-        group_name: impl Into<SharedString>,
-        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().group_hover_style = Some(GroupStyle {
-            group: group_name.into(),
-            style: f(StyleRefinement::default()),
-        });
-        self
-    }
-
-    fn on_mouse_down(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_down_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && event.button == button
-                    && bounds.contains_point(&event.position)
-                {
-                    handler(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_any_mouse_down(
-        mut self,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_down_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_any_mouse_up(
-        mut self,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_mouse_up(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble
-                    && event.button == button
-                    && bounds.contains_point(&event.position)
-                {
-                    handler(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_mouse_down_out(
-        mut self,
-        handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_down_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
-                    handler(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_mouse_up_out(
-        mut self,
-        handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_up_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
-                    handler(view, event, cx);
-                }
-            }));
-        self
-    }
-
-    fn on_mouse_move(
-        mut self,
-        handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .mouse_move_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx);
-                }
-            }));
-        self
-    }
-
-    fn on_scroll_wheel(
-        mut self,
-        handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .scroll_wheel_listeners
-            .push(Box::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    handler(view, event, cx);
-                }
-            }));
-        self
-    }
-
-    /// Capture the given action, fires during the capture phase
-    fn capture_action<A: Action>(
-        mut self,
-        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().action_listeners.push((
-            TypeId::of::<A>(),
-            Box::new(move |view, action, phase, cx| {
-                let action = action.downcast_ref().unwrap();
-                if phase == DispatchPhase::Capture {
-                    listener(view, action, cx)
-                }
-            }),
-        ));
-        self
-    }
-
-    /// Add a listener for the given action, fires during the bubble event phase
-    fn on_action<A: Action>(
-        mut self,
-        listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().action_listeners.push((
-            TypeId::of::<A>(),
-            Box::new(move |view, action, phase, cx| {
-                let action = action.downcast_ref().unwrap();
-                if phase == DispatchPhase::Bubble {
-                    listener(view, action, cx)
-                }
-            }),
-        ));
-        self
-    }
-
-    fn on_key_down(
-        mut self,
-        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .key_down_listeners
-            .push(Box::new(move |view, event, phase, cx| {
-                listener(view, event, phase, cx)
-            }));
-        self
-    }
-
-    fn on_key_up(
-        mut self,
-        listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .key_up_listeners
-            .push(Box::new(move |view, event, phase, cx| {
-                listener(view, event, phase, cx)
-            }));
-        self
-    }
-
-    fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity()
-            .drag_over_styles
-            .push((TypeId::of::<S>(), f(StyleRefinement::default())));
-        self
-    }
-
-    fn group_drag_over<S: 'static>(
-        mut self,
-        group_name: impl Into<SharedString>,
-        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().group_drag_over_styles.push((
-            TypeId::of::<S>(),
-            GroupStyle {
-                group: group_name.into(),
-                style: f(StyleRefinement::default()),
-            },
-        ));
-        self
-    }
-
-    fn on_drop<W: 'static>(
-        mut self,
-        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateless_interactivity().drop_listeners.push((
-            TypeId::of::<W>(),
-            Box::new(move |view, dragged_view, cx| {
-                listener(view, dragged_view.downcast().unwrap(), cx);
-            }),
-        ));
-        self
-    }
-}
-
-pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
-    fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
-
-    fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateful_interactivity().active_style = f(StyleRefinement::default());
-        self
-    }
-
-    fn group_active(
-        mut self,
-        group_name: impl Into<SharedString>,
-        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateful_interactivity().group_active_style = Some(GroupStyle {
-            group: group_name.into(),
-            style: f(StyleRefinement::default()),
-        });
-        self
-    }
-
-    fn on_click(
-        mut self,
-        listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.stateful_interactivity()
-            .click_listeners
-            .push(Box::new(move |view, event, cx| listener(view, event, cx)));
-        self
-    }
-
-    fn on_drag<W>(
-        mut self,
-        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-        W: 'static + Render,
-    {
-        debug_assert!(
-            self.stateful_interactivity().drag_listener.is_none(),
-            "calling on_drag more than once on the same element is not supported"
-        );
-        self.stateful_interactivity().drag_listener =
-            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
-                view: listener(view_state, cx).into(),
-                cursor_offset,
-            }));
-        self
-    }
-
-    fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
-    where
-        Self: Sized,
-    {
-        debug_assert!(
-            self.stateful_interactivity().hover_listener.is_none(),
-            "calling on_hover more than once on the same element is not supported"
-        );
-        self.stateful_interactivity().hover_listener = Some(Box::new(listener));
-        self
-    }
-
-    fn tooltip<W>(
-        mut self,
-        build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-        W: 'static + Render,
-    {
-        debug_assert!(
-            self.stateful_interactivity().tooltip_builder.is_none(),
-            "calling tooltip more than once on the same element is not supported"
-        );
-        self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
-            build_tooltip(view_state, cx).into()
-        }));
-
-        self
-    }
-}
-
-pub trait ElementInteractivity<V: 'static>: 'static {
-    fn as_stateless(&self) -> &StatelessInteractivity<V>;
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
-
-    fn refine_style(
-        &self,
-        style: &mut Style,
-        bounds: Bounds<Pixels>,
-        element_state: &InteractiveElementState,
-        cx: &mut ViewContext<V>,
-    ) {
-        let mouse_position = cx.mouse_position();
-        let stateless = self.as_stateless();
-        if let Some(group_hover) = stateless.group_hover_style.as_ref() {
-            if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
-                if group_bounds.contains_point(&mouse_position) {
-                    style.refine(&group_hover.style);
-                }
-            }
-        }
-        if bounds.contains_point(&mouse_position) {
-            style.refine(&stateless.hover_style);
-        }
-
-        if let Some(drag) = cx.active_drag.take() {
-            for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles {
-                if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
-                    if *state_type == drag.view.entity_type()
-                        && group_bounds.contains_point(&mouse_position)
-                    {
-                        style.refine(&group_drag_style.style);
-                    }
-                }
-            }
-
-            for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles {
-                if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position)
-                {
-                    style.refine(drag_over_style);
-                }
-            }
-
-            cx.active_drag = Some(drag);
-        }
-
-        if let Some(stateful) = self.as_stateful() {
-            let active_state = element_state.active_state.lock();
-            if active_state.group {
-                if let Some(group_style) = stateful.group_active_style.as_ref() {
-                    style.refine(&group_style.style);
-                }
-            }
-            if active_state.element {
-                style.refine(&stateful.active_style);
-            }
-        }
-    }
-
-    fn initialize(&mut self, cx: &mut ViewContext<V>) {
-        let stateless = self.as_stateless_mut();
-
-        for listener in stateless.key_down_listeners.drain(..) {
-            cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
-                listener(state, event, phase, cx);
-            })
-        }
-
-        for listener in stateless.key_up_listeners.drain(..) {
-            cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
-                listener(state, event, phase, cx);
-            })
-        }
-
-        for (action_type, listener) in stateless.action_listeners.drain(..) {
-            cx.on_action(action_type, listener)
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        content_size: Size<Pixels>,
-        overflow: Point<Overflow>,
-        element_state: &mut InteractiveElementState,
-        cx: &mut ViewContext<V>,
-    ) {
-        let stateless = self.as_stateless_mut();
-        for listener in stateless.mouse_down_listeners.drain(..) {
-            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.mouse_up_listeners.drain(..) {
-            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.mouse_move_listeners.drain(..) {
-            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        for listener in stateless.scroll_wheel_listeners.drain(..) {
-            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
-                listener(state, event, &bounds, phase, cx);
-            })
-        }
-
-        let hover_group_bounds = stateless
-            .group_hover_style
-            .as_ref()
-            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
-
-        if let Some(group_bounds) = hover_group_bounds {
-            let hovered = group_bounds.contains_point(&cx.mouse_position());
-            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
-                if phase == DispatchPhase::Capture {
-                    if group_bounds.contains_point(&event.position) != hovered {
-                        cx.notify();
-                    }
-                }
-            });
-        }
-
-        if stateless.hover_style.is_some()
-            || (cx.active_drag.is_some() && !stateless.drag_over_styles.is_empty())
-        {
-            let hovered = bounds.contains_point(&cx.mouse_position());
-            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
-                if phase == DispatchPhase::Capture {
-                    if bounds.contains_point(&event.position) != hovered {
-                        cx.notify();
-                    }
-                }
-            });
-        }
-
-        if cx.active_drag.is_some() {
-            let drop_listeners = mem::take(&mut stateless.drop_listeners);
-            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    if let Some(drag_state_type) =
-                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
-                    {
-                        for (drop_state_type, listener) in &drop_listeners {
-                            if *drop_state_type == drag_state_type {
-                                let drag = cx
-                                    .active_drag
-                                    .take()
-                                    .expect("checked for type drag state type above");
-                                listener(view, drag.view.clone(), cx);
-                                cx.notify();
-                                cx.stop_propagation();
-                            }
-                        }
-                    }
-                }
-            });
-        }
-
-        if let Some(stateful) = self.as_stateful_mut() {
-            let click_listeners = mem::take(&mut stateful.click_listeners);
-            let drag_listener = mem::take(&mut stateful.drag_listener);
-
-            if !click_listeners.is_empty() || drag_listener.is_some() {
-                let pending_mouse_down = element_state.pending_mouse_down.clone();
-                let mouse_down = pending_mouse_down.lock().clone();
-                if let Some(mouse_down) = mouse_down {
-                    if let Some(drag_listener) = drag_listener {
-                        let active_state = element_state.active_state.clone();
-
-                        cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
-                            if cx.active_drag.is_some() {
-                                if phase == DispatchPhase::Capture {
-                                    cx.notify();
-                                }
-                            } else if phase == DispatchPhase::Bubble
-                                && bounds.contains_point(&event.position)
-                                && (event.position - mouse_down.position).magnitude()
-                                    > DRAG_THRESHOLD
-                            {
-                                *active_state.lock() = ActiveState::default();
-                                let cursor_offset = event.position - bounds.origin;
-                                let drag = drag_listener(view_state, cursor_offset, cx);
-                                cx.active_drag = Some(drag);
-                                cx.notify();
-                                cx.stop_propagation();
-                            }
-                        });
-                    }
-
-                    cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| {
-                        if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position)
-                        {
-                            let mouse_click = ClickEvent {
-                                down: mouse_down.clone(),
-                                up: event.clone(),
-                            };
-                            for listener in &click_listeners {
-                                listener(view_state, &mouse_click, cx);
-                            }
-                        }
-                        *pending_mouse_down.lock() = None;
-                    });
-                } else {
-                    cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
-                        if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position)
-                        {
-                            *pending_mouse_down.lock() = Some(event.clone());
-                        }
-                    });
-                }
-            }
-
-            if let Some(hover_listener) = stateful.hover_listener.take() {
-                let was_hovered = element_state.hover_state.clone();
-                let has_mouse_down = element_state.pending_mouse_down.clone();
-
-                cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
-                    if phase != DispatchPhase::Bubble {
-                        return;
-                    }
-                    let is_hovered =
-                        bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
-                    let mut was_hovered = was_hovered.lock();
-
-                    if is_hovered != was_hovered.clone() {
-                        *was_hovered = is_hovered;
-                        drop(was_hovered);
-
-                        hover_listener(view_state, is_hovered, cx);
-                    }
-                });
-            }
-
-            if let Some(tooltip_builder) = stateful.tooltip_builder.take() {
-                let active_tooltip = element_state.active_tooltip.clone();
-                let pending_mouse_down = element_state.pending_mouse_down.clone();
-
-                cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
-                    if phase != DispatchPhase::Bubble {
-                        return;
-                    }
-
-                    let is_hovered = bounds.contains_point(&event.position)
-                        && pending_mouse_down.lock().is_none();
-                    if !is_hovered {
-                        active_tooltip.lock().take();
-                        return;
-                    }
-
-                    if active_tooltip.lock().is_none() {
-                        let task = cx.spawn({
-                            let active_tooltip = active_tooltip.clone();
-                            let tooltip_builder = tooltip_builder.clone();
-
-                            move |view, mut cx| async move {
-                                cx.background_executor().timer(TOOLTIP_DELAY).await;
-                                view.update(&mut cx, move |view_state, cx| {
-                                    active_tooltip.lock().replace(ActiveTooltip {
-                                        waiting: None,
-                                        tooltip: Some(AnyTooltip {
-                                            view: tooltip_builder(view_state, cx),
-                                            cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
-                                        }),
-                                    });
-                                    cx.notify();
-                                })
-                                .ok();
-                            }
-                        });
-                        active_tooltip.lock().replace(ActiveTooltip {
-                            waiting: Some(task),
-                            tooltip: None,
-                        });
-                    }
-                });
-
-                if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
-                    if active_tooltip.tooltip.is_some() {
-                        cx.active_tooltip = active_tooltip.tooltip.clone()
-                    }
-                }
-            }
-
-            let active_state = element_state.active_state.clone();
-            if active_state.lock().is_none() {
-                let active_group_bounds = stateful
-                    .group_active_style
-                    .as_ref()
-                    .and_then(|group_active| GroupBounds::get(&group_active.group, cx));
-                cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble {
-                        let group = active_group_bounds
-                            .map_or(false, |bounds| bounds.contains_point(&down.position));
-                        let element = bounds.contains_point(&down.position);
-                        if group || element {
-                            *active_state.lock() = ActiveState { group, element };
-                            cx.notify();
-                        }
-                    }
-                });
-            } else {
-                cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
-                    if phase == DispatchPhase::Capture {
-                        *active_state.lock() = ActiveState::default();
-                        cx.notify();
-                    }
-                });
-            }
-
-            if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
-                let scroll_offset = element_state
-                    .scroll_offset
-                    .get_or_insert_with(Arc::default)
-                    .clone();
-                let line_height = cx.line_height();
-                let scroll_max = (content_size - bounds.size).max(&Size::default());
-
-                cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| {
-                    if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                        let mut scroll_offset = scroll_offset.lock();
-                        let old_scroll_offset = *scroll_offset;
-                        let delta = event.delta.pixel_delta(line_height);
-
-                        if overflow.x == Overflow::Scroll {
-                            scroll_offset.x =
-                                (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.));
-                        }
-
-                        if overflow.y == Overflow::Scroll {
-                            scroll_offset.y =
-                                (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.));
-                        }
-
-                        if *scroll_offset != old_scroll_offset {
-                            cx.notify();
-                            cx.stop_propagation();
-                        }
-                    }
-                });
-            }
-        }
-    }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct StatefulInteractivity<V> {
-    pub id: ElementId,
-    #[deref]
-    #[deref_mut]
-    stateless: StatelessInteractivity<V>,
-    click_listeners: SmallVec<[ClickListener<V>; 2]>,
-    active_style: StyleRefinement,
-    group_active_style: Option<GroupStyle>,
-    drag_listener: Option<DragListener<V>>,
-    hover_listener: Option<HoverListener<V>>,
-    tooltip_builder: Option<TooltipBuilder<V>>,
-}
-
-impl<V: 'static> StatefulInteractivity<V> {
-    pub fn new(id: ElementId, stateless: StatelessInteractivity<V>) -> Self {
-        Self {
-            id,
-            stateless,
-            click_listeners: SmallVec::new(),
-            active_style: StyleRefinement::default(),
-            group_active_style: None,
-            drag_listener: None,
-            hover_listener: None,
-            tooltip_builder: None,
-        }
-    }
-}
-
-impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
-        Some(self)
-    }
-
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
-        Some(self)
-    }
-
-    fn as_stateless(&self) -> &StatelessInteractivity<V> {
-        &self.stateless
-    }
-
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
-        &mut self.stateless
-    }
-}
-
-type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
-
-pub struct StatelessInteractivity<V> {
-    pub dispatch_context: KeyContext,
-    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
-    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
-    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
-    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
-    pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
-    pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
-    pub hover_style: StyleRefinement,
-    pub group_hover_style: Option<GroupStyle>,
-    drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
-    group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
-    drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
-}
-
-impl<V> StatelessInteractivity<V> {
-    pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
-        StatefulInteractivity {
-            id: id.into(),
-            stateless: self,
-            click_listeners: SmallVec::new(),
-            drag_listener: None,
-            hover_listener: None,
-            tooltip_builder: None,
-            active_style: StyleRefinement::default(),
-            group_active_style: None,
-        }
-    }
-}
-
-pub struct GroupStyle {
-    pub group: SharedString,
-    pub style: StyleRefinement,
-}
-
-#[derive(Default)]
-pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
-
-impl GroupBounds {
-    pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
-        cx.default_global::<Self>()
-            .0
-            .get(name)
-            .and_then(|bounds_stack| bounds_stack.last())
-            .cloned()
-    }
-
-    pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
-        cx.default_global::<Self>()
-            .0
-            .entry(name)
-            .or_default()
-            .push(bounds);
-    }
-
-    pub fn pop(name: &SharedString, cx: &mut AppContext) {
-        cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
-    }
-}
-
-#[derive(Copy, Clone, Default, Eq, PartialEq)]
-struct ActiveState {
-    pub group: bool,
-    pub element: bool,
-}
-
-impl ActiveState {
-    pub fn is_none(&self) -> bool {
-        !self.group && !self.element
-    }
-}
-
-#[derive(Default)]
-pub struct InteractiveElementState {
-    active_state: Arc<Mutex<ActiveState>>,
-    hover_state: Arc<Mutex<bool>>,
-    pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
-    scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
-    active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
-}
-
-struct ActiveTooltip {
-    #[allow(unused)] // used to drop the task
-    waiting: Option<Task<()>>,
-    tooltip: Option<AnyTooltip>,
-}
-
-impl InteractiveElementState {
-    pub fn scroll_offset(&self) -> Option<Point<Pixels>> {
-        self.scroll_offset
-            .as_ref()
-            .map(|offset| offset.lock().clone())
-    }
-
-    pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
-        self.scroll_offset
-            .get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
-            .clone()
-    }
-}
-
-impl<V> Default for StatelessInteractivity<V> {
-    fn default() -> Self {
-        Self {
-            dispatch_context: KeyContext::default(),
-            mouse_down_listeners: SmallVec::new(),
-            mouse_up_listeners: SmallVec::new(),
-            mouse_move_listeners: SmallVec::new(),
-            scroll_wheel_listeners: SmallVec::new(),
-            key_down_listeners: SmallVec::new(),
-            key_up_listeners: SmallVec::new(),
-            action_listeners: SmallVec::new(),
-            hover_style: StyleRefinement::default(),
-            group_hover_style: None,
-            drag_over_styles: SmallVec::new(),
-            group_drag_over_styles: SmallVec::new(),
-            drop_listeners: SmallVec::new(),
-        }
-    }
-}
-
-impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
-    fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
-        None
-    }
-
-    fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
-        None
-    }
-
-    fn as_stateless(&self) -> &StatelessInteractivity<V> {
-        self
-    }
-
-    fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
-        self
-    }
-}
+use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct KeyDownEvent {
@@ -1021,10 +86,6 @@ where
     }
 }
 
-// impl<S, R, V, E> Render for Drag<S, R, V, E> {
-//     // fn render(&mut self, cx: ViewContext<Self>) ->
-// }
-
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 pub enum MouseButton {
     Left,
@@ -1222,45 +283,11 @@ pub struct FocusEvent {
     pub focused: Option<FocusHandle>,
 }
 
-pub type MouseDownListener<V> = Box<
-    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
-pub type MouseUpListener<V> = Box<
-    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
-
-pub type MouseMoveListener<V> = Box<
-    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
->;
-
-pub type ScrollWheelListener<V> = Box<
-    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
-        + 'static,
->;
-
-pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
-
-pub(crate) type DragListener<V> =
-    Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
-
-pub(crate) type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
-
-pub(crate) type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
-
-pub(crate) type KeyDownListener<V> =
-    Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
-
-pub(crate) type KeyUpListener<V> =
-    Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
-
-pub type ActionListener<V> =
-    Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
-
 #[cfg(test)]
 mod test {
     use crate::{
-        self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
-        StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
+        self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,
+        Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext,
     };
 
     struct TestView {

crates/gpui2/src/key_dispatch.rs 🔗

@@ -1,11 +1,9 @@
 use crate::{
-    build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
-    FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
-    Pixels, Style, StyleRefinement, ViewContext, WindowContext,
+    build_action_from_type, Action, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch,
+    Keymap, Keystroke, KeystrokeMatcher, WindowContext,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
-use refineable::Refineable;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -14,10 +12,6 @@ use std::{
 };
 use util::ResultExt;
 
-pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
-pub type FocusListener<V> =
-    Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
-
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
 pub struct DispatchNodeId(usize);
 
@@ -208,258 +202,3 @@ impl DispatchTree {
         *self.node_stack.last().unwrap()
     }
 }
-
-pub trait KeyDispatch<V: 'static>: 'static {
-    fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>>;
-    fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>>;
-    fn key_context(&self) -> &KeyContext;
-    fn key_context_mut(&mut self) -> &mut KeyContext;
-
-    fn initialize<R>(
-        &mut self,
-        focus_handle: Option<FocusHandle>,
-        cx: &mut ViewContext<V>,
-        f: impl FnOnce(Option<FocusHandle>, &mut ViewContext<V>) -> R,
-    ) -> R {
-        let focus_handle = if let Some(focusable) = self.as_focusable_mut() {
-            let focus_handle = focusable
-                .focus_handle
-                .get_or_insert_with(|| focus_handle.unwrap_or_else(|| cx.focus_handle()))
-                .clone();
-            for listener in focusable.focus_listeners.drain(..) {
-                let focus_handle = focus_handle.clone();
-                cx.on_focus_changed(move |view, event, cx| {
-                    listener(view, &focus_handle, event, cx)
-                });
-            }
-            Some(focus_handle)
-        } else {
-            None
-        };
-
-        cx.with_key_dispatch(self.key_context().clone(), focus_handle, f)
-    }
-
-    fn refine_style(&self, style: &mut Style, cx: &WindowContext) {
-        if let Some(focusable) = self.as_focusable() {
-            let focus_handle = focusable
-                .focus_handle
-                .as_ref()
-                .expect("must call initialize before refine_style");
-            if focus_handle.contains_focused(cx) {
-                style.refine(&focusable.focus_in_style);
-            }
-
-            if focus_handle.within_focused(cx) {
-                style.refine(&focusable.in_focus_style);
-            }
-
-            if focus_handle.is_focused(cx) {
-                style.refine(&focusable.focus_style);
-            }
-        }
-    }
-
-    fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
-        if let Some(focusable) = self.as_focusable() {
-            let focus_handle = focusable
-                .focus_handle
-                .clone()
-                .expect("must call initialize before paint");
-            cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
-                    if !cx.default_prevented() {
-                        cx.focus(&focus_handle);
-                        cx.prevent_default();
-                    }
-                }
-            })
-        }
-    }
-}
-
-pub struct FocusableKeyDispatch<V> {
-    pub non_focusable: NonFocusableKeyDispatch,
-    pub focus_handle: Option<FocusHandle>,
-    pub focus_listeners: FocusListeners<V>,
-    pub focus_style: StyleRefinement,
-    pub focus_in_style: StyleRefinement,
-    pub in_focus_style: StyleRefinement,
-}
-
-impl<V> FocusableKeyDispatch<V> {
-    pub fn new(non_focusable: NonFocusableKeyDispatch) -> Self {
-        Self {
-            non_focusable,
-            focus_handle: None,
-            focus_listeners: FocusListeners::default(),
-            focus_style: StyleRefinement::default(),
-            focus_in_style: StyleRefinement::default(),
-            in_focus_style: StyleRefinement::default(),
-        }
-    }
-
-    pub fn tracked(non_focusable: NonFocusableKeyDispatch, handle: &FocusHandle) -> Self {
-        Self {
-            non_focusable,
-            focus_handle: Some(handle.clone()),
-            focus_listeners: FocusListeners::default(),
-            focus_style: StyleRefinement::default(),
-            focus_in_style: StyleRefinement::default(),
-            in_focus_style: StyleRefinement::default(),
-        }
-    }
-}
-
-impl<V: 'static> KeyDispatch<V> for FocusableKeyDispatch<V> {
-    fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
-        Some(self)
-    }
-
-    fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
-        Some(self)
-    }
-
-    fn key_context(&self) -> &KeyContext {
-        &self.non_focusable.key_context
-    }
-
-    fn key_context_mut(&mut self) -> &mut KeyContext {
-        &mut self.non_focusable.key_context
-    }
-}
-
-#[derive(Default)]
-pub struct NonFocusableKeyDispatch {
-    pub(crate) key_context: KeyContext,
-}
-
-impl<V: 'static> KeyDispatch<V> for NonFocusableKeyDispatch {
-    fn as_focusable(&self) -> Option<&FocusableKeyDispatch<V>> {
-        None
-    }
-
-    fn as_focusable_mut(&mut self) -> Option<&mut FocusableKeyDispatch<V>> {
-        None
-    }
-
-    fn key_context(&self) -> &KeyContext {
-        &self.key_context
-    }
-
-    fn key_context_mut(&mut self) -> &mut KeyContext {
-        &mut self.key_context
-    }
-}
-
-pub trait Focusable<V: 'static>: Element<V> {
-    fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
-    fn set_focus_style(&mut self, style: StyleRefinement);
-    fn set_focus_in_style(&mut self, style: StyleRefinement);
-    fn set_in_focus_style(&mut self, style: StyleRefinement);
-
-    fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.set_focus_style(f(StyleRefinement::default()));
-        self
-    }
-
-    fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.set_focus_in_style(f(StyleRefinement::default()));
-        self
-    }
-
-    fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        self.set_in_focus_style(f(StyleRefinement::default()));
-        self
-    }
-
-    fn on_focus(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.focus_listeners()
-            .push(Box::new(move |view, focus_handle, event, cx| {
-                if event.focused.as_ref() == Some(focus_handle) {
-                    listener(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_blur(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.focus_listeners()
-            .push(Box::new(move |view, focus_handle, event, cx| {
-                if event.blurred.as_ref() == Some(focus_handle) {
-                    listener(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_focus_in(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.focus_listeners()
-            .push(Box::new(move |view, focus_handle, event, cx| {
-                let descendant_blurred = event
-                    .blurred
-                    .as_ref()
-                    .map_or(false, |blurred| focus_handle.contains(blurred, cx));
-                let descendant_focused = event
-                    .focused
-                    .as_ref()
-                    .map_or(false, |focused| focus_handle.contains(focused, cx));
-
-                if !descendant_blurred && descendant_focused {
-                    listener(view, event, cx)
-                }
-            }));
-        self
-    }
-
-    fn on_focus_out(
-        mut self,
-        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
-    ) -> Self
-    where
-        Self: Sized,
-    {
-        self.focus_listeners()
-            .push(Box::new(move |view, focus_handle, event, cx| {
-                let descendant_blurred = event
-                    .blurred
-                    .as_ref()
-                    .map_or(false, |blurred| focus_handle.contains(blurred, cx));
-                let descendant_focused = event
-                    .focused
-                    .as_ref()
-                    .map_or(false, |focused| focus_handle.contains(focused, cx));
-                if descendant_blurred && !descendant_focused {
-                    listener(view, event, cx)
-                }
-            }));
-        self
-    }
-}

crates/gpui2/src/prelude.rs 🔗

@@ -1 +1,4 @@
-pub use crate::{Context, ParentElement, Refineable};
+pub use crate::{
+    BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent,
+    ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext,
+};

crates/gpui2/src/style.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
     Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
     FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Result,
-    Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
+    Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext,
 };
 use refineable::{Cascade, Refineable};
 use smallvec::SmallVec;
@@ -220,7 +220,7 @@ pub struct HighlightStyle {
 impl Eq for HighlightStyle {}
 
 impl Style {
-    pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> {
+    pub fn text_style(&self) -> Option<&TextStyleRefinement> {
         if self.text.is_some() {
             Some(&self.text)
         } else {
@@ -228,13 +228,47 @@ impl Style {
         }
     }
 
+    pub fn overflow_mask(&self, bounds: Bounds<Pixels>) -> Option<ContentMask<Pixels>> {
+        match self.overflow {
+            Point {
+                x: Overflow::Visible,
+                y: Overflow::Visible,
+            } => None,
+            _ => {
+                let current_mask = bounds;
+                let min = current_mask.origin;
+                let max = current_mask.lower_right();
+                let bounds = match (
+                    self.overflow.x == Overflow::Visible,
+                    self.overflow.y == Overflow::Visible,
+                ) {
+                    // x and y both visible
+                    (true, true) => return None,
+                    // x visible, y hidden
+                    (true, false) => Bounds::from_corners(
+                        point(min.x, bounds.origin.y),
+                        point(max.x, bounds.lower_right().y),
+                    ),
+                    // x hidden, y visible
+                    (false, true) => Bounds::from_corners(
+                        point(bounds.origin.x, min.y),
+                        point(bounds.lower_right().x, max.y),
+                    ),
+                    // both hidden
+                    (false, false) => bounds,
+                };
+                Some(ContentMask { bounds })
+            }
+        }
+    }
+
     pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
     where
         C: BorrowAppContext,
         F: FnOnce(&mut C) -> R,
     {
         if self.text.is_some() {
-            cx.with_text_style(self.text.clone(), f)
+            cx.with_text_style(Some(self.text.clone()), f)
         } else {
             f(cx)
         }
@@ -274,7 +308,7 @@ impl Style {
             bounds: mask_bounds,
         };
 
-        cx.with_content_mask(mask, f)
+        cx.with_content_mask(Some(mask), f)
     }
 
     /// Paints the background of an element styled with this style.

crates/gpui2/src/styled.rs 🔗

@@ -1,26 +1,24 @@
 use crate::{
     self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
     DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
-    SharedString, Style, StyleRefinement, Visibility,
+    SharedString, StyleRefinement, Visibility,
 };
 use crate::{BoxShadow, TextStyleRefinement};
-use refineable::Refineable;
 use smallvec::{smallvec, SmallVec};
+use taffy::style::Overflow;
 
-pub trait Styled {
+pub trait Styled: Sized {
     fn style(&mut self) -> &mut StyleRefinement;
 
-    fn computed_style(&mut self) -> Style {
-        Style::default().refined(self.style().clone())
-    }
-
     gpui2_macros::style_helpers!();
 
+    fn z_index(mut self, z_index: u32) -> Self {
+        self.style().z_index = Some(z_index);
+        self
+    }
+
     /// Sets the size of the element to the full width and height.
-    fn full(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn full(mut self) -> Self {
         self.style().size.width = Some(relative(1.).into());
         self.style().size.height = Some(relative(1.).into());
         self
@@ -28,118 +26,98 @@ pub trait Styled {
 
     /// Sets the position of the element to `relative`.
     /// [Docs](https://tailwindcss.com/docs/position)
-    fn relative(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn relative(mut self) -> Self {
         self.style().position = Some(Position::Relative);
         self
     }
 
     /// Sets the position of the element to `absolute`.
     /// [Docs](https://tailwindcss.com/docs/position)
-    fn absolute(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn absolute(mut self) -> Self {
         self.style().position = Some(Position::Absolute);
         self
     }
 
     /// Sets the display type of the element to `block`.
     /// [Docs](https://tailwindcss.com/docs/display)
-    fn block(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn block(mut self) -> Self {
         self.style().display = Some(Display::Block);
         self
     }
 
     /// Sets the display type of the element to `flex`.
     /// [Docs](https://tailwindcss.com/docs/display)
-    fn flex(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex(mut self) -> Self {
         self.style().display = Some(Display::Flex);
         self
     }
 
     /// Sets the visibility of the element to `visible`.
     /// [Docs](https://tailwindcss.com/docs/visibility)
-    fn visible(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn visible(mut self) -> Self {
         self.style().visibility = Some(Visibility::Visible);
         self
     }
 
     /// Sets the visibility of the element to `hidden`.
     /// [Docs](https://tailwindcss.com/docs/visibility)
-    fn invisible(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn invisible(mut self) -> Self {
         self.style().visibility = Some(Visibility::Hidden);
         self
     }
 
-    fn cursor(mut self, cursor: CursorStyle) -> Self
-    where
-        Self: Sized,
-    {
+    fn overflow_hidden(mut self) -> Self {
+        self.style().overflow.x = Some(Overflow::Hidden);
+        self.style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    fn overflow_hidden_x(mut self) -> Self {
+        self.style().overflow.x = Some(Overflow::Hidden);
+        self
+    }
+
+    fn overflow_hidden_y(mut self) -> Self {
+        self.style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    fn cursor(mut self, cursor: CursorStyle) -> Self {
         self.style().mouse_cursor = Some(cursor);
         self
     }
 
     /// Sets the cursor style when hovering an element to `default`.
     /// [Docs](https://tailwindcss.com/docs/cursor)
-    fn cursor_default(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn cursor_default(mut self) -> Self {
         self.style().mouse_cursor = Some(CursorStyle::Arrow);
         self
     }
 
     /// Sets the cursor style when hovering an element to `pointer`.
     /// [Docs](https://tailwindcss.com/docs/cursor)
-    fn cursor_pointer(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn cursor_pointer(mut self) -> Self {
         self.style().mouse_cursor = Some(CursorStyle::PointingHand);
         self
     }
 
     /// Sets the flex direction of the element to `column`.
     /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
-    fn flex_col(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_col(mut self) -> Self {
         self.style().flex_direction = Some(FlexDirection::Column);
         self
     }
 
     /// Sets the flex direction of the element to `row`.
     /// [Docs](https://tailwindcss.com/docs/flex-direction#row)
-    fn flex_row(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_row(mut self) -> Self {
         self.style().flex_direction = Some(FlexDirection::Row);
         self
     }
 
     /// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
     /// [Docs](https://tailwindcss.com/docs/flex#flex-1)
-    fn flex_1(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_1(mut self) -> Self {
         self.style().flex_grow = Some(1.);
         self.style().flex_shrink = Some(1.);
         self.style().flex_basis = Some(relative(0.).into());
@@ -148,10 +126,7 @@ pub trait Styled {
 
     /// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
     /// [Docs](https://tailwindcss.com/docs/flex#auto)
-    fn flex_auto(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_auto(mut self) -> Self {
         self.style().flex_grow = Some(1.);
         self.style().flex_shrink = Some(1.);
         self.style().flex_basis = Some(Length::Auto);
@@ -160,10 +135,7 @@ pub trait Styled {
 
     /// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
     /// [Docs](https://tailwindcss.com/docs/flex#initial)
-    fn flex_initial(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_initial(mut self) -> Self {
         self.style().flex_grow = Some(0.);
         self.style().flex_shrink = Some(1.);
         self.style().flex_basis = Some(Length::Auto);
@@ -172,10 +144,7 @@ pub trait Styled {
 
     /// Sets the element to prevent a flex item from growing or shrinking.
     /// [Docs](https://tailwindcss.com/docs/flex#none)
-    fn flex_none(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn flex_none(mut self) -> Self {
         self.style().flex_grow = Some(0.);
         self.style().flex_shrink = Some(0.);
         self
@@ -183,40 +152,28 @@ pub trait Styled {
 
     /// Sets the element to allow a flex item to grow to fill any available space.
     /// [Docs](https://tailwindcss.com/docs/flex-grow)
-    fn grow(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn grow(mut self) -> Self {
         self.style().flex_grow = Some(1.);
         self
     }
 
     /// Sets the element to align flex items to the start of the container's cross axis.
     /// [Docs](https://tailwindcss.com/docs/align-items#start)
-    fn items_start(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn items_start(mut self) -> Self {
         self.style().align_items = Some(AlignItems::FlexStart);
         self
     }
 
     /// Sets the element to align flex items to the end of the container's cross axis.
     /// [Docs](https://tailwindcss.com/docs/align-items#end)
-    fn items_end(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn items_end(mut self) -> Self {
         self.style().align_items = Some(AlignItems::FlexEnd);
         self
     }
 
     /// Sets the element to align flex items along the center of the container's cross axis.
     /// [Docs](https://tailwindcss.com/docs/align-items#center)
-    fn items_center(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn items_center(mut self) -> Self {
         self.style().align_items = Some(AlignItems::Center);
         self
     }
@@ -224,40 +181,28 @@ pub trait Styled {
     /// Sets the element to justify flex items along the container's main axis
     /// such that there is an equal amount of space between each item.
     /// [Docs](https://tailwindcss.com/docs/justify-content#space-between)
-    fn justify_between(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn justify_between(mut self) -> Self {
         self.style().justify_content = Some(JustifyContent::SpaceBetween);
         self
     }
 
     /// Sets the element to justify flex items along the center of the container's main axis.
     /// [Docs](https://tailwindcss.com/docs/justify-content#center)
-    fn justify_center(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn justify_center(mut self) -> Self {
         self.style().justify_content = Some(JustifyContent::Center);
         self
     }
 
     /// Sets the element to justify flex items against the start of the container's main axis.
     /// [Docs](https://tailwindcss.com/docs/justify-content#start)
-    fn justify_start(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn justify_start(mut self) -> Self {
         self.style().justify_content = Some(JustifyContent::Start);
         self
     }
 
     /// Sets the element to justify flex items against the end of the container's main axis.
     /// [Docs](https://tailwindcss.com/docs/justify-content#end)
-    fn justify_end(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn justify_end(mut self) -> Self {
         self.style().justify_content = Some(JustifyContent::End);
         self
     }
@@ -265,10 +210,7 @@ pub trait Styled {
     /// Sets the element to justify items along the container's main axis such
     /// that there is an equal amount of space on each side of each item.
     /// [Docs](https://tailwindcss.com/docs/justify-content#space-around)
-    fn justify_around(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn justify_around(mut self) -> Self {
         self.style().justify_content = Some(JustifyContent::SpaceAround);
         self
     }
@@ -295,30 +237,21 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self {
         self.style().box_shadow = Some(shadows);
         self
     }
 
     /// Clears the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_none(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_none(mut self) -> Self {
         self.style().box_shadow = Some(Default::default());
         self
     }
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_sm(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_sm(mut self) -> Self {
         self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
             color: hsla(0., 0., 0., 0.05),
             offset: point(px(0.), px(1.)),
@@ -330,10 +263,7 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_md(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_md(mut self) -> Self {
         self.style().box_shadow = Some(smallvec![
             BoxShadow {
                 color: hsla(0.5, 0., 0., 0.1),
@@ -353,10 +283,7 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_lg(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_lg(mut self) -> Self {
         self.style().box_shadow = Some(smallvec![
             BoxShadow {
                 color: hsla(0., 0., 0., 0.1),
@@ -376,10 +303,7 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_xl(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_xl(mut self) -> Self {
         self.style().box_shadow = Some(smallvec![
             BoxShadow {
                 color: hsla(0., 0., 0., 0.1),
@@ -399,10 +323,7 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow_2xl(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn shadow_2xl(mut self) -> Self {
         self.style().box_shadow = Some(smallvec![BoxShadow {
             color: hsla(0., 0., 0., 0.25),
             offset: point(px(0.), px(25.)),
@@ -417,198 +338,138 @@ pub trait Styled {
         &mut style.text
     }
 
-    fn text_color(mut self, color: impl Into<Hsla>) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_color(mut self, color: impl Into<Hsla>) -> Self {
         self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
         self
     }
 
-    fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(size.into());
         self
     }
 
-    fn text_xs(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_xs(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(0.75).into());
         self
     }
 
-    fn text_sm(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_sm(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(0.875).into());
         self
     }
 
-    fn text_base(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_base(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(1.0).into());
         self
     }
 
-    fn text_lg(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_lg(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(1.125).into());
         self
     }
 
-    fn text_xl(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_xl(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(1.25).into());
         self
     }
 
-    fn text_2xl(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_2xl(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(1.5).into());
         self
     }
 
-    fn text_3xl(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_3xl(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_size = Some(rems(1.875).into());
         self
     }
 
-    fn text_decoration_none(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_none(mut self) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .underline = None;
         self
     }
 
-    fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.color = Some(color.into());
         self
     }
 
-    fn text_decoration_solid(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_solid(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.wavy = false;
         self
     }
 
-    fn text_decoration_wavy(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_wavy(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.wavy = true;
         self
     }
 
-    fn text_decoration_0(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_0(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.thickness = px(0.);
         self
     }
 
-    fn text_decoration_1(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_1(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.thickness = px(1.);
         self
     }
 
-    fn text_decoration_2(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_2(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.thickness = px(2.);
         self
     }
 
-    fn text_decoration_4(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_4(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.thickness = px(4.);
         self
     }
 
-    fn text_decoration_8(mut self) -> Self
-    where
-        Self: Sized,
-    {
+    fn text_decoration_8(mut self) -> Self {
         let style = self.text_style().get_or_insert_with(Default::default);
         let underline = style.underline.get_or_insert_with(Default::default);
         underline.thickness = px(8.);
         self
     }
 
-    fn font(mut self, family_name: impl Into<SharedString>) -> Self
-    where
-        Self: Sized,
-    {
+    fn font(mut self, family_name: impl Into<SharedString>) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .font_family = Some(family_name.into());
         self
     }
 
-    fn line_height(mut self, line_height: impl Into<DefiniteLength>) -> Self
-    where
-        Self: Sized,
-    {
+    fn line_height(mut self, line_height: impl Into<DefiniteLength>) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)
             .line_height = Some(line_height.into());

crates/gpui2/src/view.rs 🔗

@@ -206,7 +206,7 @@ impl<V: Render> From<View<V>> for AnyView {
 impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
     type ElementState = Box<dyn Any>;
 
-    fn id(&self) -> Option<ElementId> {
+    fn element_id(&self) -> Option<ElementId> {
         Some(self.model.entity_id.into())
     }
 
@@ -286,7 +286,7 @@ mod any_view {
     use std::any::Any;
 
     pub(crate) fn initialize<V: Render>(view: &AnyView, cx: &mut WindowContext) -> Box<dyn Any> {
-        cx.with_element_id(view.model.entity_id, |_, cx| {
+        cx.with_element_id(Some(view.model.entity_id), |cx| {
             let view = view.clone().downcast::<V>().unwrap();
             let element = view.update(cx, |view, cx| {
                 let mut element = AnyElement::new(view.render(cx));
@@ -302,7 +302,7 @@ mod any_view {
         element: &mut Box<dyn Any>,
         cx: &mut WindowContext,
     ) -> LayoutId {
-        cx.with_element_id(view.model.entity_id, |_, cx| {
+        cx.with_element_id(Some(view.model.entity_id), |cx| {
             let view = view.clone().downcast::<V>().unwrap();
             let element = element.downcast_mut::<AnyElement<V>>().unwrap();
             view.update(cx, |view, cx| element.layout(view, cx))
@@ -314,7 +314,7 @@ mod any_view {
         element: &mut Box<dyn Any>,
         cx: &mut WindowContext,
     ) {
-        cx.with_element_id(view.model.entity_id, |_, cx| {
+        cx.with_element_id(Some(view.model.entity_id), |cx| {
             let view = view.clone().downcast::<V>().unwrap();
             let element = element.downcast_mut::<AnyElement<V>>().unwrap();
             view.update(cx, |view, cx| element.paint(view, cx))

crates/gpui2/src/window.rs 🔗

@@ -422,11 +422,8 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
-        dbg!("BEFORE FOCUS");
         if let Some(focus_handle) = self.focused() {
-            dbg!("BEFORE DEFER", focus_handle.id);
             self.defer(move |cx| {
-                dbg!("AFTER DEFER");
                 if let Some(node_id) = cx
                     .window
                     .current_frame
@@ -1077,7 +1074,7 @@ impl<'a> WindowContext<'a> {
         if let Some(active_drag) = self.app.active_drag.take() {
             self.with_z_index(1, |cx| {
                 let offset = cx.mouse_position() - active_drag.cursor_offset;
-                cx.with_element_offset(Some(offset), |cx| {
+                cx.with_element_offset(offset, |cx| {
                     let available_space =
                         size(AvailableSpace::MinContent, AvailableSpace::MinContent);
                     active_drag.view.draw(available_space, cx);
@@ -1086,7 +1083,7 @@ impl<'a> WindowContext<'a> {
             });
         } else if let Some(active_tooltip) = self.app.active_tooltip.take() {
             self.with_z_index(1, |cx| {
-                cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
+                cx.with_element_offset(active_tooltip.cursor_offset, |cx| {
                     let available_space =
                         size(AvailableSpace::MinContent, AvailableSpace::MinContent);
                     active_tooltip.view.draw(available_space, cx);
@@ -1584,43 +1581,50 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
     /// used to associate state with identified elements across separate frames.
     fn with_element_id<R>(
         &mut self,
-        id: impl Into<ElementId>,
-        f: impl FnOnce(GlobalElementId, &mut Self) -> R,
+        id: Option<impl Into<ElementId>>,
+        f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        let window = self.window_mut();
-        window.element_id_stack.push(id.into());
-        let global_id = window.element_id_stack.clone();
-        let result = f(global_id, self);
-        let window: &mut Window = self.borrow_mut();
-        window.element_id_stack.pop();
-        result
+        if let Some(id) = id.map(Into::into) {
+            let window = self.window_mut();
+            window.element_id_stack.push(id.into());
+            let result = f(self);
+            let window: &mut Window = self.borrow_mut();
+            window.element_id_stack.pop();
+            result
+        } else {
+            f(self)
+        }
     }
 
     /// Invoke the given function with the given content mask after intersecting it
     /// with the current mask.
     fn with_content_mask<R>(
         &mut self,
-        mask: ContentMask<Pixels>,
+        mask: Option<ContentMask<Pixels>>,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        let mask = mask.intersect(&self.content_mask());
-        self.window_mut()
-            .current_frame
-            .content_mask_stack
-            .push(mask);
-        let result = f(self);
-        self.window_mut().current_frame.content_mask_stack.pop();
-        result
+        if let Some(mask) = mask {
+            let mask = mask.intersect(&self.content_mask());
+            self.window_mut()
+                .current_frame
+                .content_mask_stack
+                .push(mask);
+            let result = f(self);
+            self.window_mut().current_frame.content_mask_stack.pop();
+            result
+        } else {
+            f(self)
+        }
     }
 
     /// Update the global element offset based on the given offset. This is used to implement
     /// scrolling and position drag handles.
     fn with_element_offset<R>(
         &mut self,
-        offset: Option<Point<Pixels>>,
+        offset: Point<Pixels>,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        let Some(offset) = offset else {
+        if offset.is_zero() {
             return f(self);
         };
 
@@ -1656,7 +1660,9 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
     where
         S: 'static,
     {
-        self.with_element_id(id, |global_id, cx| {
+        self.with_element_id(Some(id), |cx| {
+            let global_id = cx.window().element_id_stack.clone();
+
             if let Some(any) = cx
                 .window_mut()
                 .current_frame
@@ -2084,11 +2090,10 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
     ) -> R {
         let window = &mut self.window;
-
         window
             .current_frame
             .dispatch_tree
-            .push_node(context, &mut window.previous_frame.dispatch_tree);
+            .push_node(context.clone(), &mut window.previous_frame.dispatch_tree);
         if let Some(focus_handle) = focus_handle.as_ref() {
             window
                 .current_frame

crates/gpui2_macros/src/style_helpers.rs 🔗

@@ -130,7 +130,7 @@ fn generate_predefined_setter(
 
     let method = quote! {
         #[doc = #doc_string]
-        fn #method_name(mut self) -> Self where Self: std::marker::Sized {
+        fn #method_name(mut self) -> Self {
             let style = self.style();
             #(#field_assignments)*
             self
@@ -163,7 +163,7 @@ fn generate_custom_value_setter(
 
     let method = quote! {
         #[doc = #doc_string]
-        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self where Self: std::marker::Sized {
+        fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self {
             let style = self.style();
             #(#field_assignments)*
             self

crates/picker2/src/picker2.rs 🔗

@@ -1,7 +1,7 @@
 use editor::Editor;
 use gpui::{
-    div, uniform_list, Component, Div, MouseButton, ParentElement, Render, StatelessInteractive,
-    Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
+    div, prelude::*, uniform_list, Component, Div, MouseButton, Render, Task,
+    UniformListScrollHandle, View, ViewContext, WindowContext,
 };
 use std::{cmp, sync::Arc};
 use ui::{prelude::*, v_stack, Divider, Label, TextColor};
@@ -179,7 +179,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
-            .context("picker")
+            .key_context("picker")
             .size_full()
             .elevation_2(cx)
             .on_action(Self::select_next)

crates/project_panel2/src/project_panel.rs 🔗

@@ -9,10 +9,10 @@ use file_associations::FileAssociations;
 use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
-    ClipboardItem, Component, Div, EventEmitter, FocusHandle, FocusableKeyDispatch, Model,
-    MouseButton, ParentElement as _, Pixels, Point, PromptLevel, Render, StatefulInteractive,
-    StatefulInteractivity, StatelessInteractive, Styled, Task, UniformListScrollHandle, View,
-    ViewContext, VisualContext as _, WeakView, WindowContext,
+    ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, InteractiveComponent,
+    Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render, Stateful,
+    StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View, ViewContext,
+    VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -1372,7 +1372,7 @@ impl ProjectPanel {
         details: EntryDetails,
         // dragged_entry_destination: &mut Option<Arc<Path>>,
         cx: &mut ViewContext<Self>,
-    ) -> Div<Self, StatefulInteractivity<Self>> {
+    ) -> Stateful<Self, Div<Self>> {
         let kind = details.kind;
         let settings = ProjectPanelSettings::get_global(cx);
         const INDENT_SIZE: Pixels = px(16.0);
@@ -1418,7 +1418,7 @@ impl ProjectPanel {
 }
 
 impl Render for ProjectPanel {
-    type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
+    type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
 
     fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let has_worktree = self.visible_entries.len() != 0;
@@ -1427,7 +1427,7 @@ impl Render for ProjectPanel {
             div()
                 .id("project-panel")
                 .size_full()
-                .context("ProjectPanel")
+                .key_context("ProjectPanel")
                 .on_action(Self::select_next)
                 .on_action(Self::select_prev)
                 .on_action(Self::expand_selected_entry)

crates/storybook2/src/stories/colors.rs 🔗

@@ -1,5 +1,5 @@
 use crate::story::Story;
-use gpui::{px, Div, Render};
+use gpui::{prelude::*, px, Div, Render};
 use theme2::{default_color_scales, ColorScaleStep};
 use ui::prelude::*;
 

crates/storybook2/src/stories/focus.rs 🔗

@@ -1,6 +1,5 @@
 use gpui::{
-    actions, div, Div, FocusHandle, Focusable, FocusableKeyDispatch, KeyBinding, ParentElement,
-    Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext,
+    actions, div, prelude::*, Div, FocusHandle, Focusable, KeyBinding, Render, Stateful, View,
     WindowContext,
 };
 use theme2::ActiveTheme;
@@ -28,7 +27,7 @@ impl FocusStory {
 }
 
 impl Render for FocusStory {
-    type Element = Div<Self, StatefulInteractivity<Self>, FocusableKeyDispatch<Self>>;
+    type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();
@@ -42,7 +41,7 @@ impl Render for FocusStory {
         div()
             .id("parent")
             .focusable()
-            .context("parent")
+            .key_context("parent")
             .on_action(|_, action: &ActionA, cx| {
                 println!("Action A dispatched on parent");
             })
@@ -62,7 +61,7 @@ impl Render for FocusStory {
             .child(
                 div()
                     .track_focus(&self.child_1_focus)
-                    .context("child-1")
+                    .key_context("child-1")
                     .on_action(|_, action: &ActionB, cx| {
                         println!("Action B dispatched on child 1 during");
                     })
@@ -82,7 +81,7 @@ impl Render for FocusStory {
             .child(
                 div()
                     .track_focus(&self.child_2_focus)
-                    .context("child-2")
+                    .key_context("child-2")
                     .on_action(|_, action: &ActionC, cx| {
                         println!("Action C dispatched on child 2");
                     })

crates/storybook2/src/stories/kitchen_sink.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{story::Story, story_selector::ComponentStory};
-use gpui::{Div, Render, StatefulInteractivity, View, VisualContext};
+use gpui::{prelude::*, Div, Render, Stateful, View};
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 
@@ -12,7 +12,7 @@ impl KitchenSinkStory {
 }
 
 impl Render for KitchenSinkStory {
-    type Element = Div<Self, StatefulInteractivity<Self>>;
+    type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let component_stories = ComponentStory::iter()

crates/storybook2/src/stories/picker.rs 🔗

@@ -1,11 +1,7 @@
-use std::sync::Arc;
-
 use fuzzy::StringMatchCandidate;
-use gpui::{
-    div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task,
-    View, VisualContext, WindowContext,
-};
+use gpui::{div, prelude::*, Div, KeyBinding, Render, Styled, Task, View, WindowContext};
 use picker::{Picker, PickerDelegate};
+use std::sync::Arc;
 use theme2::ActiveTheme;
 
 pub struct PickerStory {

crates/storybook2/src/stories/scroll.rs 🔗

@@ -1,7 +1,4 @@
-use gpui::{
-    div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled,
-    View, VisualContext, WindowContext,
-};
+use gpui::{div, prelude::*, px, Div, Render, SharedString, Stateful, Styled, View, WindowContext};
 use theme2::ActiveTheme;
 
 pub struct ScrollStory;
@@ -13,7 +10,7 @@ impl ScrollStory {
 }
 
 impl Render for ScrollStory {
-    type Element = Div<Self, StatefulInteractivity<Self>>;
+    type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
         let theme = cx.theme();

crates/storybook2/src/stories/text.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
+use gpui::{div, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext};
 
 pub struct TextStory;
 

crates/theme2/src/story.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Component, Div, ParentElement, Styled, ViewContext};
+use gpui::{div, Component, Div, ParentComponent, Styled, ViewContext};
 
 use crate::ActiveTheme;
 

crates/theme2/src/styles/players.rs 🔗

@@ -143,7 +143,7 @@ use crate::{amber, blue, jade, lime, orange, pink, purple, red};
 mod stories {
     use super::*;
     use crate::{ActiveTheme, Story};
-    use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
+    use gpui::{div, img, px, Div, ParentComponent, Render, Styled, ViewContext};
 
     pub struct PlayerStory;
 

crates/ui2/src/components/button.rs 🔗

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use gpui::{div, DefiniteLength, Hsla, MouseButton, WindowContext};
+use gpui::{div, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext};
 
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor};

crates/ui2/src/components/checkbox.rs 🔗

@@ -1,9 +1,5 @@
+use gpui::{div, prelude::*, Component, ElementId, Styled, ViewContext};
 use std::sync::Arc;
-
-use gpui::{
-    div, Component, ElementId, ParentElement, StatefulInteractive, StatelessInteractive, Styled,
-    ViewContext,
-};
 use theme2::ActiveTheme;
 
 use crate::{Icon, IconElement, Selection, TextColor};

crates/ui2/src/components/elevated_surface.rs 🔗

@@ -23,6 +23,6 @@ pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<
         .shadow(level.shadow())
 }
 
-pub fn modal<V>(cx: &mut ViewContext<V>) -> Div<V> {
+pub fn modal<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
     elevated_surface(ElevationIndex::ModalSurface, cx)
 }

crates/ui2/src/components/icon_button.rs 🔗

@@ -1,5 +1,5 @@
-use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement, TextColor, TextTooltip};
-use gpui::{MouseButton, VisualContext};
+use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement, TextTooltip};
+use gpui::{prelude::*, MouseButton, VisualContext};
 use std::sync::Arc;
 
 struct IconButtonHandlers<V: 'static> {

crates/ui2/src/components/input.rs 🔗

@@ -1,6 +1,5 @@
-use crate::prelude::*;
-use crate::Label;
-use crate::TextColor;
+use crate::{prelude::*, Label};
+use gpui::prelude::*;
 
 #[derive(Default, PartialEq)]
 pub enum InputVariant {

crates/ui2/src/components/modal.rs 🔗

@@ -74,7 +74,7 @@ impl<V: 'static> Modal<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for Modal<V> {
+impl<V: 'static> ParentComponent<V> for Modal<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }

crates/ui2/src/components/palette.rs 🔗

@@ -1,5 +1,5 @@
-use crate::prelude::*;
-use crate::{h_stack, v_stack, KeyBinding, Label, TextColor};
+use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct Palette {

crates/ui2/src/components/panel.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{AbsoluteLength, AnyElement};
+use gpui::{prelude::*, AbsoluteLength, AnyElement};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -113,7 +113,7 @@ impl<V: 'static> Panel<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for Panel<V> {
+impl<V: 'static> ParentComponent<V> for Panel<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
@@ -126,7 +126,7 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Label, Story};
-    use gpui::{Div, Render};
+    use gpui::{Div, InteractiveComponent, Render};
 
     pub struct PanelStory;
 

crates/ui2/src/components/tab.rs 🔗

@@ -1,6 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconElement, Label, TextColor};
-use gpui::{red, Div, ElementId, Render, View, VisualContext};
+use gpui::{prelude::*, red, Div, ElementId, Render, View};
 
 #[derive(Component, Clone)]
 pub struct Tab {

crates/ui2/src/components/toast.rs 🔗

@@ -1,7 +1,6 @@
-use gpui::AnyElement;
-use smallvec::SmallVec;
-
 use crate::prelude::*;
+use gpui::{prelude::*, AnyElement};
+use smallvec::SmallVec;
 
 #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
 pub enum ToastOrigin {
@@ -59,7 +58,7 @@ impl<V: 'static> Toast<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for Toast<V> {
+impl<V: 'static> ParentComponent<V> for Toast<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }

crates/ui2/src/components/toggle.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Component, ParentElement};
+use gpui::{div, Component, ParentComponent};
 
 use crate::{Icon, IconElement, IconSize, TextColor};
 

crates/ui2/src/prelude.rs 🔗

@@ -1,8 +1,8 @@
 use gpui::rems;
 use gpui::Rems;
 pub use gpui::{
-    div, Component, Element, ElementId, ParentElement, SharedString, StatefulInteractive,
-    StatelessInteractive, Styled, ViewContext, WindowContext,
+    div, Component, Element, ElementId, InteractiveComponent, ParentComponent, SharedString,
+    Styled, ViewContext, WindowContext,
 };
 
 pub use crate::elevation::*;

crates/ui2/src/styled_ext.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{Div, ElementInteractivity, KeyDispatch, Styled, UniformList, ViewContext};
+use gpui::{Styled, ViewContext};
 use theme2::ActiveTheme;
 
 use crate::{ElevationIndex, UITextSize};
@@ -93,11 +93,4 @@ pub trait StyledExt: Styled + Sized {
     }
 }
 
-impl<V, I, F> StyledExt for Div<V, I, F>
-where
-    I: ElementInteractivity<V>,
-    F: KeyDispatch<V>,
-{
-}
-
-impl<V> StyledExt for UniformList<V> {}
+impl<E: Styled> StyledExt for E {}

crates/ui2/src/to_extract/assistant_panel.rs 🔗

@@ -1,6 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconButton, Label, Panel, PanelSide};
-use gpui::{rems, AbsoluteLength};
+use gpui::{prelude::*, rems, AbsoluteLength};
 
 #[derive(Component)]
 pub struct AssistantPanel {

crates/ui2/src/to_extract/breadcrumb.rs 🔗

@@ -1,9 +1,7 @@
+use crate::{h_stack, prelude::*, HighlightedText};
+use gpui::{prelude::*, Div};
 use std::path::PathBuf;
 
-use crate::prelude::*;
-use crate::{h_stack, HighlightedText};
-use gpui::Div;
-
 #[derive(Clone)]
 pub struct Symbol(pub Vec<HighlightedText>);
 

crates/ui2/src/to_extract/chat_panel.rs 🔗

@@ -1,7 +1,6 @@
+use crate::{prelude::*, Icon, IconButton, Input, Label};
 use chrono::NaiveDateTime;
-
-use crate::prelude::*;
-use crate::{Icon, IconButton, Input, Label, TextColor};
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct ChatPanel {

crates/ui2/src/to_extract/collab_panel.rs 🔗

@@ -1,7 +1,8 @@
-use crate::{prelude::*, Toggle};
 use crate::{
-    static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, ListHeader,
+    prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon,
+    List, ListHeader, Toggle,
 };
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct CollabPanel {

crates/ui2/src/to_extract/editor_pane.rs 🔗

@@ -60,12 +60,12 @@ impl Render for EditorPane {
                 Toolbar::new()
                     .left_item(Breadcrumb::new(self.path.clone(), self.symbols.clone()))
                     .right_items(vec![
-                        IconButton::new("toggle_inlay_hints", Icon::InlayHint),
+                        IconButton::<Self>::new("toggle_inlay_hints", Icon::InlayHint),
                         IconButton::<Self>::new("buffer_search", Icon::MagnifyingGlass)
                             .when(self.is_buffer_search_open, |this| {
                                 this.color(TextColor::Accent)
                             })
-                            .on_click(|editor, cx| {
+                            .on_click(|editor: &mut Self, cx| {
                                 editor.toggle_buffer_search(cx);
                             }),
                         IconButton::new("inline_assist", Icon::MagicWand),

crates/ui2/src/to_extract/notifications_panel.rs 🔗

@@ -1,10 +1,9 @@
-use crate::utils::naive_format_distance_from_now;
 use crate::{
-    h_stack, prelude::*, static_new_notification_items_2, v_stack, Avatar, ButtonOrIconButton,
-    Icon, IconElement, Label, LineHeightStyle, ListHeaderMeta, ListSeparator, PublicPlayer,
-    TextColor, UnreadIndicator,
+    h_stack, prelude::*, static_new_notification_items_2, utils::naive_format_distance_from_now,
+    v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle,
+    ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator,
 };
-use crate::{ClickHandler, ListHeader};
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct NotificationsPanel {

crates/ui2/src/to_extract/panes.rs 🔗

@@ -59,7 +59,7 @@ impl<V: 'static> Pane<V> {
     }
 }
 
-impl<V: 'static> ParentElement<V> for Pane<V> {
+impl<V: 'static> ParentComponent<V> for Pane<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }

crates/ui2/src/to_extract/project_panel.rs 🔗

@@ -1,7 +1,8 @@
-use crate::prelude::*;
 use crate::{
-    static_project_panel_project_items, static_project_panel_single_items, Input, List, ListHeader,
+    prelude::*, static_project_panel_project_items, static_project_panel_single_items, Input, List,
+    ListHeader,
 };
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct ProjectPanel {

crates/ui2/src/to_extract/status_bar.rs 🔗

@@ -112,7 +112,7 @@ impl StatusBar {
                     .when(workspace.is_project_panel_open(), |this| {
                         this.color(TextColor::Accent)
                     })
-                    .on_click(|workspace, cx| {
+                    .on_click(|workspace: &mut Workspace, cx| {
                         workspace.toggle_project_panel(cx);
                     }),
             )
@@ -121,7 +121,7 @@ impl StatusBar {
                     .when(workspace.is_collab_panel_open(), |this| {
                         this.color(TextColor::Accent)
                     })
-                    .on_click(|workspace, cx| {
+                    .on_click(|workspace: &mut Workspace, cx| {
                         workspace.toggle_collab_panel();
                     }),
             )
@@ -176,7 +176,7 @@ impl StatusBar {
                             .when(workspace.is_terminal_open(), |this| {
                                 this.color(TextColor::Accent)
                             })
-                            .on_click(|workspace, cx| {
+                            .on_click(|workspace: &mut Workspace, cx| {
                                 workspace.toggle_terminal(cx);
                             }),
                     )
@@ -185,7 +185,7 @@ impl StatusBar {
                             .when(workspace.is_chat_panel_open(), |this| {
                                 this.color(TextColor::Accent)
                             })
-                            .on_click(|workspace, cx| {
+                            .on_click(|workspace: &mut Workspace, cx| {
                                 workspace.toggle_chat_panel(cx);
                             }),
                     )
@@ -194,7 +194,7 @@ impl StatusBar {
                             .when(workspace.is_assistant_panel_open(), |this| {
                                 this.color(TextColor::Accent)
                             })
-                            .on_click(|workspace, cx| {
+                            .on_click(|workspace: &mut Workspace, cx| {
                                 workspace.toggle_assistant_panel(cx);
                             }),
                     ),

crates/ui2/src/to_extract/tab_bar.rs 🔗

@@ -1,5 +1,5 @@
-use crate::prelude::*;
-use crate::{Icon, IconButton, Tab};
+use crate::{prelude::*, Icon, IconButton, Tab};
+use gpui::prelude::*;
 
 #[derive(Component)]
 pub struct TabBar {

crates/ui2/src/to_extract/title_bar.rs 🔗

@@ -153,12 +153,16 @@ impl Render for TitleBar {
                             .child(
                                 IconButton::<TitleBar>::new("toggle_mic_status", Icon::Mic)
                                     .when(self.is_mic_muted(), |this| this.color(TextColor::Error))
-                                    .on_click(|title_bar, cx| title_bar.toggle_mic_status(cx)),
+                                    .on_click(|title_bar: &mut TitleBar, cx| {
+                                        title_bar.toggle_mic_status(cx)
+                                    }),
                             )
                             .child(
                                 IconButton::<TitleBar>::new("toggle_deafened", Icon::AudioOn)
                                     .when(self.is_deafened, |this| this.color(TextColor::Error))
-                                    .on_click(|title_bar, cx| title_bar.toggle_deafened(cx)),
+                                    .on_click(|title_bar: &mut TitleBar, cx| {
+                                        title_bar.toggle_deafened(cx)
+                                    }),
                             )
                             .child(
                                 IconButton::<TitleBar>::new("toggle_screen_share", Icon::Screen)
@@ -166,7 +170,7 @@ impl Render for TitleBar {
                                         self.screen_share_status == ScreenShareStatus::Shared,
                                         |this| this.color(TextColor::Accent),
                                     )
-                                    .on_click(|title_bar, cx| {
+                                    .on_click(|title_bar: &mut TitleBar, cx| {
                                         title_bar.toggle_screen_share_status(cx)
                                     }),
                             ),

crates/workspace2/src/dock.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{status_bar::StatusItemView, Axis, Workspace};
 use gpui::{
     div, px, Action, AnyView, AppContext, Component, Div, Entity, EntityId, EventEmitter,
-    FocusHandle, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
+    FocusHandle, ParentComponent, Render, Styled, Subscription, View, ViewContext, WeakView,
     WindowContext,
 };
 use schemars::JsonSchema;

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{
-    div, px, AnyView, Div, EventEmitter, FocusHandle, ParentElement, Render, StatelessInteractive,
-    Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
+    div, prelude::*, px, AnyView, Div, EventEmitter, FocusHandle, Render, Subscription, View,
+    ViewContext, WindowContext,
 };
 use ui::{h_stack, v_stack};
 

crates/workspace2/src/pane.rs 🔗

@@ -1,5 +1,3 @@
-// mod dragged_item_receiver;
-
 use crate::{
     item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
     toolbar::Toolbar,
@@ -9,7 +7,7 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    actions, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
+    actions, prelude::*, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId,
     EventEmitter, FocusHandle, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext,
     WeakView, WindowContext,
 };
@@ -1919,7 +1917,7 @@ impl Render for Pane {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         v_stack()
-            .context("Pane")
+            .key_context("Pane")
             .size_full()
             .on_action(|pane: &mut Self, action, cx| {
                 pane.close_active_item(action, cx)

crates/workspace2/src/pane/dragged_item_receiver.rs 🔗

@@ -2,7 +2,7 @@ use super::DraggedItem;
 use crate::{Pane, SplitDirection, Workspace};
 use gpui::{
     color::Color,
-    elements::{Canvas, MouseEventHandler, ParentElement, Stack},
+    elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
     geometry::{rect::RectF, vector::Vector2F},
     platform::MouseButton,
     scene::MouseUp,

crates/workspace2/src/status_bar.rs 🔗

@@ -2,7 +2,7 @@ use std::any::TypeId;
 
 use crate::{ItemHandle, Pane};
 use gpui::{
-    div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View, ViewContext,
+    div, AnyView, Component, Div, ParentComponent, Render, Styled, Subscription, View, ViewContext,
     WindowContext,
 };
 use theme2::ActiveTheme;

crates/workspace2/src/workspace2.rs 🔗

@@ -36,12 +36,11 @@ use futures::{
     Future, FutureExt, StreamExt,
 };
 use gpui::{
-    actions, div, point, rems, size, Action, AnyModel, AnyView, AnyWeakView, AppContext,
-    AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId, EventEmitter,
-    FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentElement, Point, Render, Size,
-    StatefulInteractive, StatelessInteractive, StatelessInteractivity, Styled, Subscription, Task,
-    View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
-    WindowOptions,
+    actions, div, point, prelude::*, rems, size, Action, AnyModel, AnyView, AnyWeakView,
+    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Component, Div, Entity, EntityId,
+    EventEmitter, FocusHandle, GlobalPixels, KeyContext, Model, ModelContext, ParentComponent,
+    Point, Render, Size, Styled, Subscription, Task, View, ViewContext, WeakView, WindowBounds,
+    WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -348,7 +347,6 @@ struct Follower {
 impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
-        use gpui::Context;
         use node_runtime::FakeNodeRuntime;
         use settings2::SettingsStore;
 
@@ -436,13 +434,7 @@ pub enum Event {
 pub struct Workspace {
     weak_self: WeakView<Self>,
     focus_handle: FocusHandle,
-    workspace_actions: Vec<
-        Box<
-            dyn Fn(
-                Div<Workspace, StatelessInteractivity<Workspace>>,
-            ) -> Div<Workspace, StatelessInteractivity<Workspace>>,
-        >,
-    >,
+    workspace_actions: Vec<Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>>,
     zoomed: Option<AnyWeakView>,
     zoomed_position: Option<DockPosition>,
     center: PaneGroup,
@@ -3412,7 +3404,6 @@ impl Workspace {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
-        use gpui::Context;
         use node_runtime::FakeNodeRuntime;
 
         let client = project.read(cx).client();
@@ -3477,10 +3468,7 @@ impl Workspace {
         self
     }
 
-    fn add_workspace_actions_listeners(
-        &self,
-        mut div: Div<Workspace, StatelessInteractivity<Workspace>>,
-    ) -> Div<Workspace, StatelessInteractivity<Workspace>> {
+    fn add_workspace_actions_listeners(&self, mut div: Div<Workspace>) -> Div<Workspace> {
         for action in self.workspace_actions.iter() {
             div = (action)(div)
         }
@@ -3717,7 +3705,7 @@ impl Render for Workspace {
         let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
 
         self.add_workspace_actions_listeners(div())
-            .context(context)
+            .key_context(context)
             .relative()
             .size_full()
             .flex()