Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/app/entity_map.rs |  10 +
crates/gpui3/src/element.rs        |  37 ++++
crates/gpui3/src/elements/div.rs   | 232 ++++++++++++++++++++-----------
crates/gpui3/src/elements/img.rs   |  22 +-
crates/gpui3/src/elements/svg.rs   |  22 +-
crates/gpui3/src/interactive.rs    |  41 +++--
crates/gpui3/src/window.rs         | 122 ++++++++++++++--
7 files changed, 342 insertions(+), 144 deletions(-)

Detailed changes

crates/gpui3/src/app/entity_map.rs 🔗

@@ -199,6 +199,16 @@ pub struct WeakHandle<T> {
     entity_map: Weak<RwLock<EntityMapState>>,
 }
 
+impl<T: 'static + Send + Sync> Clone for WeakHandle<T> {
+    fn clone(&self) -> Self {
+        Self {
+            id: self.id,
+            entity_type: self.entity_type,
+            entity_map: self.entity_map.clone(),
+        }
+    }
+}
+
 impl<T: Send + Sync + 'static> WeakHandle<T> {
     pub fn upgrade(&self, _: &impl Context) -> Option<Handle<T>> {
         let entity_map = &self.entity_map.upgrade()?;

crates/gpui3/src/element.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, ViewContext};
+use crate::{BorrowWindow, Bounds, ElementId, FocusHandle, LayoutId, Pixels, Point, ViewContext};
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
 
@@ -31,21 +31,48 @@ pub trait ElementIdentity: 'static + Send + Sync {
     fn id(&self) -> Option<ElementId>;
 }
 
-pub struct IdentifiedElement(pub(crate) ElementId);
-pub struct AnonymousElement;
+pub struct Identified(pub(crate) ElementId);
 
-impl ElementIdentity for IdentifiedElement {
+impl ElementIdentity for Identified {
     fn id(&self) -> Option<ElementId> {
         Some(self.0.clone())
     }
 }
 
-impl ElementIdentity for AnonymousElement {
+pub struct Anonymous;
+
+impl ElementIdentity for Anonymous {
     fn id(&self) -> Option<ElementId> {
         None
     }
 }
 
+pub trait ElementFocusability: 'static + Send + Sync {
+    fn focus_handle(&self) -> Option<&FocusHandle>;
+}
+
+pub struct Focusable(FocusHandle);
+
+impl ElementFocusability for Focusable {
+    fn focus_handle(&self) -> Option<&FocusHandle> {
+        Some(&self.0)
+    }
+}
+
+impl From<FocusHandle> for Focusable {
+    fn from(value: FocusHandle) -> Self {
+        Self(value)
+    }
+}
+
+pub struct NonFocusable;
+
+impl ElementFocusability for NonFocusable {
+    fn focus_handle(&self) -> Option<&FocusHandle> {
+        None
+    }
+}
+
 pub trait ParentElement: Element {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
 

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

@@ -1,9 +1,9 @@
 use crate::{
-    Active, AnonymousElement, AnyElement, AppContext, BorrowWindow, Bounds, Click, DispatchPhase,
-    Element, ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement,
-    LayoutId, MouseClickEvent, MouseDownEvent, MouseEventListeners, MouseMoveEvent, MouseUpEvent,
-    Overflow, ParentElement, Pixels, Point, ScrollWheelEvent, SharedString, Style, StyleRefinement,
-    Styled, ViewContext,
+    Active, Anonymous, AnyElement, AppContext, BorrowWindow, Bounds, Click, DispatchPhase, Element,
+    ElementFocusability, ElementId, ElementIdentity, EventListeners, FocusHandle, Focusable, Hover,
+    Identified, Interactive, IntoAnyElement, KeyDownEvent, LayoutId, MouseClickEvent,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, NonFocusable, Overflow, ParentElement, Pixels,
+    Point, ScrollWheelEvent, SharedString, Style, StyleRefinement, Styled, ViewContext,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -60,12 +60,13 @@ impl ScrollState {
     }
 }
 
-pub fn div<S>() -> Div<S, AnonymousElement>
+pub fn div<V>() -> Div<Anonymous, NonFocusable, V>
 where
-    S: 'static + Send + Sync,
+    V: 'static + Send + Sync,
 {
     Div {
-        kind: AnonymousElement,
+        identity: Anonymous,
+        focusability: NonFocusable,
         children: SmallVec::new(),
         group: None,
         base_style: StyleRefinement::default(),
@@ -73,12 +74,13 @@ where
         group_hover: None,
         active_style: StyleRefinement::default(),
         group_active: None,
-        listeners: MouseEventListeners::default(),
+        listeners: EventListeners::default(),
     }
 }
 
-pub struct Div<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
-    kind: K,
+pub struct Div<I: ElementIdentity, F: ElementFocusability, V: 'static + Send + Sync> {
+    identity: I,
+    focusability: F,
     children: SmallVec<[AnyElement<V>; 2]>,
     group: Option<SharedString>,
     base_style: StyleRefinement,
@@ -86,7 +88,7 @@ pub struct Div<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement>
     group_hover: Option<GroupStyle>,
     active_style: StyleRefinement,
     group_active: Option<GroupStyle>,
-    listeners: MouseEventListeners<V>,
+    listeners: EventListeners<V>,
 }
 
 struct GroupStyle {
@@ -94,13 +96,15 @@ struct GroupStyle {
     style: StyleRefinement,
 }
 
-impl<V> Div<V, AnonymousElement>
+impl<F, V> Div<Anonymous, F, V>
 where
+    F: ElementFocusability,
     V: 'static + Send + Sync,
 {
-    pub fn id(self, id: impl Into<ElementId>) -> Div<V, IdentifiedElement> {
+    pub fn id(self, id: impl Into<ElementId>) -> Div<Identified, F, V> {
         Div {
-            kind: IdentifiedElement(id.into()),
+            identity: Identified(id.into()),
+            focusability: self.focusability,
             children: self.children,
             group: self.group,
             base_style: self.base_style,
@@ -113,10 +117,11 @@ where
     }
 }
 
-impl<V, K> Div<V, K>
+impl<I, F, V> Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     pub fn group(mut self, group: impl Into<SharedString>) -> Self {
         self.group = Some(group.into());
@@ -313,16 +318,55 @@ where
     }
 }
 
-impl<V, K> Element for Div<V, K>
+impl<I, V> Div<I, NonFocusable, V>
+where
+    I: ElementIdentity,
+    V: 'static + Send + Sync,
+{
+    pub fn focusable(self, handle: &FocusHandle) -> Div<I, Focusable, V> {
+        Div {
+            identity: self.identity,
+            focusability: handle.clone().into(),
+            children: self.children,
+            group: self.group,
+            base_style: self.base_style,
+            hover_style: self.hover_style,
+            group_hover: self.group_hover,
+            active_style: self.active_style,
+            group_active: self.group_active,
+            listeners: self.listeners,
+        }
+    }
+}
+
+impl<I, V> Div<I, Focusable, V>
 where
+    I: ElementIdentity,
+    V: 'static + Send + Sync,
+{
+    pub fn on_key_down<F>(
+        mut self,
+        listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self {
+        self.listeners.key_down.push(Arc::new(listener));
+        self
+    }
+}
+
+impl<I, F, V> Element for Div<I, F, V>
+where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     type ViewState = V;
     type ElementState = DivState;
 
     fn id(&self) -> Option<ElementId> {
-        self.kind.id()
+        self.identity.id()
     }
 
     fn layout(
@@ -355,105 +399,121 @@ where
         cx: &mut ViewContext<Self::ViewState>,
     ) {
         self.with_element_id(cx, |this, cx| {
-            if let Some(group) = this.group.clone() {
-                cx.default_global::<GroupBounds>()
-                    .0
-                    .entry(group)
-                    .or_default()
-                    .push(bounds);
-            }
-
-            let hover_group_bounds = this
-                .group_hover
-                .as_ref()
-                .and_then(|group_hover| group_bounds(&group_hover.group, cx));
-            let active_group_bounds = this
-                .group_active
-                .as_ref()
-                .and_then(|group_active| group_bounds(&group_active.group, cx));
-            let style = this.compute_style(bounds, element_state, cx);
-            let z_index = style.z_index.unwrap_or(0);
-
-            // Paint background and event handlers.
-            cx.stack(z_index, |cx| {
-                cx.stack(0, |cx| {
-                    style.paint(bounds, cx);
-                    this.paint_hover_listeners(bounds, hover_group_bounds, cx);
-                    this.paint_active_listener(
-                        bounds,
-                        active_group_bounds,
-                        element_state.active_state.clone(),
-                        cx,
-                    );
-                    this.paint_event_listeners(bounds, element_state.pending_click.clone(), cx);
-                });
-
-                cx.stack(1, |cx| {
-                    style.apply_text_style(cx, |cx| {
-                        style.apply_overflow(bounds, cx, |cx| {
-                            for child in &mut this.children {
-                                child.paint(view_state, None, cx);
-                            }
-                        })
-                    })
-                });
-            });
+            cx.with_key_listeners(
+                this.focusability.focus_handle().cloned(),
+                this.listeners.key_down.clone(),
+                this.listeners.key_up.clone(),
+                |cx| {
+                    if let Some(group) = this.group.clone() {
+                        cx.default_global::<GroupBounds>()
+                            .0
+                            .entry(group)
+                            .or_default()
+                            .push(bounds);
+                    }
 
-            if let Some(group) = this.group.as_ref() {
-                cx.default_global::<GroupBounds>()
-                    .0
-                    .get_mut(group)
-                    .unwrap()
-                    .pop();
-            }
+                    let hover_group_bounds = this
+                        .group_hover
+                        .as_ref()
+                        .and_then(|group_hover| group_bounds(&group_hover.group, cx));
+                    let active_group_bounds = this
+                        .group_active
+                        .as_ref()
+                        .and_then(|group_active| group_bounds(&group_active.group, cx));
+                    let style = this.compute_style(bounds, element_state, cx);
+                    let z_index = style.z_index.unwrap_or(0);
+
+                    // Paint background and event handlers.
+                    cx.stack(z_index, |cx| {
+                        cx.stack(0, |cx| {
+                            style.paint(bounds, cx);
+                            this.paint_hover_listeners(bounds, hover_group_bounds, cx);
+                            this.paint_active_listener(
+                                bounds,
+                                active_group_bounds,
+                                element_state.active_state.clone(),
+                                cx,
+                            );
+                            this.paint_event_listeners(
+                                bounds,
+                                element_state.pending_click.clone(),
+                                cx,
+                            );
+                        });
+
+                        cx.stack(1, |cx| {
+                            style.apply_text_style(cx, |cx| {
+                                style.apply_overflow(bounds, cx, |cx| {
+                                    for child in &mut this.children {
+                                        child.paint(view_state, None, cx);
+                                    }
+                                })
+                            })
+                        });
+                    });
+
+                    if let Some(group) = this.group.as_ref() {
+                        cx.default_global::<GroupBounds>()
+                            .0
+                            .get_mut(group)
+                            .unwrap()
+                            .pop();
+                    }
+                },
+            )
         })
     }
 }
 
-impl<V, K> IntoAnyElement<V> for Div<V, K>
+impl<I, F, V> IntoAnyElement<V> for Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     fn into_any(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<V, K> ParentElement for Div<V, K>
+impl<I, F, V> ParentElement for Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
         &mut self.children
     }
 }
 
-impl<V, K> Styled for Div<V, K>
+impl<I, F, V> Styled for Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.base_style
     }
 }
 
-impl<V, K> Interactive for Div<V, K>
+impl<I, F, V> Interactive for Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
-    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+    fn listeners(&mut self) -> &mut EventListeners<V> {
         &mut self.listeners
     }
 }
 
-impl<V, K> Hover for Div<V, K>
+impl<I, F, V> Hover for Div<I, F, V>
 where
+    I: ElementIdentity,
+    F: ElementFocusability,
     V: 'static + Send + Sync,
-    K: ElementIdentity,
 {
     fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
         if let Some(group) = group {
@@ -464,10 +524,16 @@ where
     }
 }
 
-impl<V> Click for Div<V, IdentifiedElement> where V: 'static + Send + Sync {}
+impl<F, V> Click for Div<Identified, F, V>
+where
+    F: ElementFocusability,
+    V: 'static + Send + Sync,
+{
+}
 
-impl<V> Active for Div<V, IdentifiedElement>
+impl<F, V> Active for Div<Identified, F, V>
 where
+    F: ElementFocusability,
     V: 'static + Send + Sync,
 {
     fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {

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

@@ -1,18 +1,18 @@
 use crate::{
-    div, Active, AnonymousElement, AnyElement, BorrowWindow, Bounds, Click, Div, DivState, Element,
-    ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
-    MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled, ViewContext,
+    div, Active, Anonymous, AnyElement, BorrowWindow, Bounds, Click, Div, DivState, Element,
+    ElementId, ElementIdentity, EventListeners, Hover, Identified, Interactive, IntoAnyElement,
+    LayoutId, NonFocusable, Pixels, SharedString, StyleRefinement, Styled, ViewContext,
 };
 use futures::FutureExt;
 use util::ResultExt;
 
-pub struct Img<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
-    base: Div<V, K>,
+pub struct Img<V: 'static + Send + Sync, K: ElementIdentity = Anonymous> {
+    base: Div<K, NonFocusable, V>,
     uri: Option<SharedString>,
     grayscale: bool,
 }
 
-pub fn img<V>() -> Img<V, AnonymousElement>
+pub fn img<V>() -> Img<V, Anonymous>
 where
     V: 'static + Send + Sync,
 {
@@ -39,8 +39,8 @@ where
     }
 }
 
-impl<V: 'static + Send + Sync> Img<V, AnonymousElement> {
-    pub fn id(self, id: impl Into<ElementId>) -> Img<V, IdentifiedElement> {
+impl<V: 'static + Send + Sync> Img<V, Anonymous> {
+    pub fn id(self, id: impl Into<ElementId>) -> Img<V, Identified> {
         Img {
             base: self.base.id(id),
             uri: self.uri,
@@ -136,7 +136,7 @@ where
     V: 'static + Send + Sync,
     K: ElementIdentity,
 {
-    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+    fn listeners(&mut self) -> &mut EventListeners<V> {
         self.base.listeners()
     }
 }
@@ -151,9 +151,9 @@ where
     }
 }
 
-impl<V> Click for Img<V, IdentifiedElement> where V: 'static + Send + Sync {}
+impl<V> Click for Img<V, Identified> where V: 'static + Send + Sync {}
 
-impl<V> Active for Img<V, IdentifiedElement>
+impl<V> Active for Img<V, Identified>
 where
     V: 'static + Send + Sync,
 {

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

@@ -1,16 +1,16 @@
 use crate::{
-    div, Active, AnonymousElement, AnyElement, Bounds, Click, Div, DivState, Element, ElementId,
-    ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
-    MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled,
+    div, Active, Anonymous, AnyElement, Bounds, Click, Div, DivState, Element, ElementId,
+    ElementIdentity, EventListeners, Hover, Identified, Interactive, IntoAnyElement, LayoutId,
+    NonFocusable, Pixels, SharedString, StyleRefinement, Styled,
 };
 use util::ResultExt;
 
-pub struct Svg<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
-    base: Div<V, K>,
+pub struct Svg<V: 'static + Send + Sync, K: ElementIdentity = Anonymous> {
+    base: Div<K, NonFocusable, V>,
     path: Option<SharedString>,
 }
 
-pub fn svg<V>() -> Svg<V, AnonymousElement>
+pub fn svg<V>() -> Svg<V, Anonymous>
 where
     V: 'static + Send + Sync,
 {
@@ -31,8 +31,8 @@ where
     }
 }
 
-impl<V: 'static + Send + Sync> Svg<V, AnonymousElement> {
-    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, IdentifiedElement> {
+impl<V: 'static + Send + Sync> Svg<V, Anonymous> {
+    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, Identified> {
         Svg {
             base: self.base.id(id),
             path: self.path,
@@ -110,7 +110,7 @@ where
     V: 'static + Send + Sync,
     K: ElementIdentity,
 {
-    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+    fn listeners(&mut self) -> &mut EventListeners<V> {
         self.base.listeners()
     }
 }
@@ -125,9 +125,9 @@ where
     }
 }
 
-impl<V> Click for Svg<V, IdentifiedElement> where V: 'static + Send + Sync {}
+impl<V> Click for Svg<V, Identified> where V: 'static + Send + Sync {}
 
-impl<V> Active for Svg<V, IdentifiedElement>
+impl<V> Active for Svg<V, Identified>
 where
     V: 'static + Send + Sync,
 {

crates/gpui3/src/interactive.rs 🔗

@@ -1,13 +1,13 @@
 use smallvec::SmallVec;
 
 use crate::{
-    Bounds, DispatchPhase, Element, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    Pixels, ScrollWheelEvent, ViewContext,
+    Bounds, DispatchPhase, Element, KeyDownEvent, KeyUpEvent, MouseButton, MouseDownEvent,
+    MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ViewContext,
 };
 use std::sync::Arc;
 
 pub trait Interactive: Element {
-    fn listeners(&mut self) -> &mut MouseEventListeners<Self::ViewState>;
+    fn listeners(&mut self) -> &mut EventListeners<Self::ViewState>;
 
     fn on_mouse_down(
         mut self,
@@ -164,43 +164,52 @@ pub trait Click: Interactive {
     }
 }
 
-type MouseDownHandler<V> = Arc<
+type MouseDownListener<V> = Arc<
     dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
-type MouseUpHandler<V> = Arc<
+type MouseUpListener<V> = Arc<
     dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
-type MouseClickHandler<V> =
+type MouseClickListener<V> =
     Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
 
-type MouseMoveHandler<V> = Arc<
+type MouseMoveListener<V> = Arc<
     dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
-type ScrollWheelHandler<V> = Arc<
+
+type ScrollWheelListener<V> = Arc<
     dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
 
-pub struct MouseEventListeners<V: 'static> {
-    pub mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
-    pub mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
-    pub mouse_click: SmallVec<[MouseClickHandler<V>; 2]>,
-    pub mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
-    pub scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
+pub type KeyDownListener<V> =
+    Arc<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub type KeyUpListener<V> =
+    Arc<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub struct EventListeners<V: 'static> {
+    pub mouse_down: SmallVec<[MouseDownListener<V>; 2]>,
+    pub mouse_up: SmallVec<[MouseUpListener<V>; 2]>,
+    pub mouse_click: SmallVec<[MouseClickListener<V>; 2]>,
+    pub mouse_move: SmallVec<[MouseMoveListener<V>; 2]>,
+    pub scroll_wheel: SmallVec<[ScrollWheelListener<V>; 2]>,
+    pub key_down: SmallVec<[KeyDownListener<V>; 2]>,
+    pub key_up: SmallVec<[KeyUpListener<V>; 2]>,
 }
 
-impl<V> Default for MouseEventListeners<V> {
+impl<V> Default for EventListeners<V> {
     fn default() -> Self {
         Self {
             mouse_down: SmallVec::new(),
@@ -208,6 +217,8 @@ impl<V> Default for MouseEventListeners<V> {
             mouse_click: SmallVec::new(),
             mouse_move: SmallVec::new(),
             scroll_wheel: SmallVec::new(),
+            key_down: SmallVec::new(),
+            key_up: SmallVec::new(),
         }
     }
 }

crates/gpui3/src/window.rs 🔗

@@ -2,11 +2,11 @@ use crate::{
     px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
     Event, EventEmitter, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, IsZero,
-    LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
-    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
-    WindowOptions, SUBPIXEL_VARIANTS,
+    KeyDownEvent, KeyDownListener, KeyUpEvent, KeyUpListener, LayoutId, MainThread, MainThreadOnly,
+    MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point,
+    PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams,
+    ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine,
+    Task, Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -21,7 +21,7 @@ use std::{
     mem,
     sync::Arc,
 };
-use util::ResultExt;
+use util::{post_inc, ResultExt};
 
 #[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)]
 pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>);
@@ -42,6 +42,14 @@ pub enum DispatchPhase {
 type MouseEventHandler =
     Arc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
 
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct FocusId(usize);
+
+#[derive(Clone)]
+pub struct FocusHandle {
+    id: FocusId,
+}
+
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
@@ -57,11 +65,18 @@ pub struct Window {
     z_index_stack: StackingOrder,
     content_mask_stack: Vec<ContentMask<Pixels>>,
     mouse_event_handlers: HashMap<TypeId, Vec<(StackingOrder, MouseEventHandler)>>,
+    key_down_listener_stack:
+        Vec<Arc<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>>,
+    key_up_listener_stack:
+        Vec<Arc<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>>,
     propagate_event: bool,
     mouse_position: Point<Pixels>,
     scale_factor: f32,
     pub(crate) scene_builder: SceneBuilder,
     pub(crate) dirty: bool,
+    focus: Option<FocusId>,
+    next_focus_id: FocusId,
+    painted_focused_element: bool,
 }
 
 impl Window {
@@ -121,11 +136,16 @@ impl Window {
             z_index_stack: StackingOrder(SmallVec::new()),
             content_mask_stack: Vec::new(),
             mouse_event_handlers: HashMap::default(),
+            key_down_listener_stack: Vec::new(),
+            key_up_listener_stack: Vec::new(),
             propagate_event: true,
             mouse_position,
             scale_factor,
             scene_builder: SceneBuilder::new(),
             dirty: true,
+            focus: None,
+            next_focus_id: FocusId(0),
+            painted_focused_element: false,
         }
     }
 }
@@ -166,6 +186,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.dirty = true;
     }
 
+    pub fn focus_handle(&mut self) -> FocusHandle {
+        let id = FocusId(post_inc(&mut self.window.next_focus_id.0));
+        FocusHandle { id }
+    }
+
     pub fn run_on_main<R>(
         &mut self,
         f: impl FnOnce(&mut MainThread<WindowContext<'_, '_>>) -> R + Send + 'static,
@@ -645,14 +670,16 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         // reference during the upcoming frame.
         let window = &mut *self.window;
         mem::swap(&mut window.element_states, &mut window.prev_element_states);
-        self.window.element_states.clear();
+        window.element_states.clear();
 
         // Clear mouse event listeners, because elements add new element listeners
         // when the upcoming frame is painted.
-        self.window
+        window
             .mouse_event_handlers
             .values_mut()
             .for_each(Vec::clear);
+
+        window.painted_focused_element = false;
     }
 
     fn end_frame(&mut self) {
@@ -882,7 +909,7 @@ impl<S> BorrowWindow for ViewContext<'_, '_, S> {
     }
 }
 
-impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
+impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
     fn mutable(app: &'a mut AppContext, window: &'w mut Window, entity_id: EntityId) -> Self {
         Self {
             window_cx: WindowContext::mutable(app, window),
@@ -891,7 +918,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         }
     }
 
-    pub fn handle(&self) -> WeakHandle<S> {
+    pub fn handle(&self) -> WeakHandle<V> {
         self.entities.weak_handle(self.entity_id)
     }
 
@@ -902,7 +929,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         result
     }
 
-    pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext<S>) + Send + 'static) {
+    pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext<V>) + Send + 'static) {
         let entity = self.handle();
         self.window_cx.on_next_frame(move |cx| {
             entity.update(cx, f).ok();
@@ -912,7 +939,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     pub fn observe<E: Send + Sync + 'static>(
         &mut self,
         handle: &Handle<E>,
-        on_notify: impl Fn(&mut S, Handle<E>, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
+        on_notify: impl Fn(&mut V, Handle<E>, &mut ViewContext<'_, '_, V>) + Send + Sync + 'static,
     ) -> Subscription {
         let this = self.handle();
         let handle = handle.downgrade();
@@ -936,7 +963,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     pub fn subscribe<E: EventEmitter + Send + Sync + 'static>(
         &mut self,
         handle: &Handle<E>,
-        on_event: impl Fn(&mut S, Handle<E>, &E::Event, &mut ViewContext<'_, '_, S>)
+        on_event: impl Fn(&mut V, Handle<E>, &E::Event, &mut ViewContext<'_, '_, V>)
             + Send
             + Sync
             + 'static,
@@ -963,7 +990,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
 
     pub fn on_release(
         &mut self,
-        on_release: impl Fn(&mut S, &mut WindowContext) + Send + Sync + 'static,
+        on_release: impl Fn(&mut V, &mut WindowContext) + Send + Sync + 'static,
     ) -> Subscription {
         let window_handle = self.window.handle;
         self.app.release_handlers.insert(
@@ -979,7 +1006,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     pub fn observe_release<E: Send + Sync + 'static>(
         &mut self,
         handle: &Handle<E>,
-        on_release: impl Fn(&mut S, &mut E, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
+        on_release: impl Fn(&mut V, &mut E, &mut ViewContext<'_, '_, V>) + Send + Sync + 'static,
     ) -> Subscription {
         let this = self.handle();
         let window_handle = self.window.handle;
@@ -1002,10 +1029,67 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         });
     }
 
+    pub fn with_key_listeners<R>(
+        &mut self,
+        focus_handle: Option<FocusHandle>,
+        key_down: impl IntoIterator<Item = KeyDownListener<V>>,
+        key_up: impl IntoIterator<Item = KeyUpListener<V>>,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        let Some(focus_handle) = focus_handle else {
+            return f(self);
+        };
+        let Some(focused_id) = self.window.focus else {
+            return f(self);
+        };
+        if self.window.painted_focused_element {
+            return f(self);
+        }
+
+        let prev_key_down_len = self.window.key_down_listener_stack.len();
+        let prev_key_up_len = self.window.key_up_listener_stack.len();
+
+        for listener in key_down {
+            let handle = self.handle();
+            self.window
+                .key_down_listener_stack
+                .push(Arc::new(move |event, phase, cx| {
+                    handle
+                        .update(cx, |view, cx| listener(view, event, phase, cx))
+                        .log_err();
+                }));
+        }
+        for listener in key_up {
+            let handle = self.handle();
+            self.window
+                .key_up_listener_stack
+                .push(Arc::new(move |event, phase, cx| {
+                    handle
+                        .update(cx, |view, cx| listener(view, event, phase, cx))
+                        .log_err();
+                }));
+        }
+
+        if focus_handle.id == focused_id {
+            self.window.painted_focused_element = true;
+        }
+
+        let result = f(self);
+
+        if focus_handle.id != focused_id {
+            self.window
+                .key_down_listener_stack
+                .truncate(prev_key_down_len);
+            self.window.key_up_listener_stack.truncate(prev_key_up_len);
+        }
+
+        result
+    }
+
     pub fn run_on_main<R>(
         &mut self,
-        view: &mut S,
-        f: impl FnOnce(&mut S, &mut MainThread<ViewContext<'_, '_, S>>) -> R + Send + 'static,
+        view: &mut V,
+        f: impl FnOnce(&mut V, &mut MainThread<ViewContext<'_, '_, V>>) -> R + Send + 'static,
     ) -> Task<Result<R>>
     where
         R: Send + 'static,
@@ -1021,7 +1105,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
 
     pub fn spawn<Fut, R>(
         &mut self,
-        f: impl FnOnce(WeakHandle<S>, AsyncWindowContext) -> Fut + Send + 'static,
+        f: impl FnOnce(WeakHandle<V>, AsyncWindowContext) -> Fut + Send + 'static,
     ) -> Task<R>
     where
         R: Send + 'static,
@@ -1036,7 +1120,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
 
     pub fn on_mouse_event<Event: 'static>(
         &mut self,
-        handler: impl Fn(&mut S, &Event, DispatchPhase, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static,
     ) {
         let handle = self.handle().upgrade(self).unwrap();
         self.window_cx.on_mouse_event(move |event, phase, cx| {