diff --git a/crates/gpui3/src/app/entity_map.rs b/crates/gpui3/src/app/entity_map.rs index ce111c019d..a756fe0bc8 100644 --- a/crates/gpui3/src/app/entity_map.rs +++ b/crates/gpui3/src/app/entity_map.rs @@ -199,6 +199,16 @@ pub struct WeakHandle { entity_map: Weak>, } +impl Clone for WeakHandle { + fn clone(&self) -> Self { + Self { + id: self.id, + entity_type: self.entity_type, + entity_map: self.entity_map.clone(), + } + } +} + impl WeakHandle { pub fn upgrade(&self, _: &impl Context) -> Option> { let entity_map = &self.entity_map.upgrade()?; diff --git a/crates/gpui3/src/element.rs b/crates/gpui3/src/element.rs index b9c42721d1..888010dc06 100644 --- a/crates/gpui3/src/element.rs +++ b/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; } -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 { Some(self.0.clone()) } } -impl ElementIdentity for AnonymousElement { +pub struct Anonymous; + +impl ElementIdentity for Anonymous { fn id(&self) -> Option { 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 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; 2]>; diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index d110d81132..78743f7089 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/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() -> Div +pub fn div() -> Div 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 { - kind: K, +pub struct Div { + identity: I, + focusability: F, children: SmallVec<[AnyElement; 2]>, group: Option, base_style: StyleRefinement, @@ -86,7 +88,7 @@ pub struct Div group_hover: Option, active_style: StyleRefinement, group_active: Option, - listeners: MouseEventListeners, + listeners: EventListeners, } struct GroupStyle { @@ -94,13 +96,15 @@ struct GroupStyle { style: StyleRefinement, } -impl Div +impl Div where + F: ElementFocusability, V: 'static + Send + Sync, { - pub fn id(self, id: impl Into) -> Div { + pub fn id(self, id: impl Into) -> Div { 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 Div +impl Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { pub fn group(mut self, group: impl Into) -> Self { self.group = Some(group.into()); @@ -313,16 +318,55 @@ where } } -impl Element for Div +impl Div where + I: ElementIdentity, + V: 'static + Send + Sync, +{ + pub fn focusable(self, handle: &FocusHandle) -> Div { + 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 Div +where + I: ElementIdentity, + V: 'static + Send + Sync, +{ + pub fn on_key_down( + mut self, + listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self { + self.listeners.key_down.push(Arc::new(listener)); + self + } +} + +impl Element for Div +where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { type ViewState = V; type ElementState = DivState; fn id(&self) -> Option { - self.kind.id() + self.identity.id() } fn layout( @@ -355,105 +399,121 @@ where cx: &mut ViewContext, ) { self.with_element_id(cx, |this, cx| { - if let Some(group) = this.group.clone() { - cx.default_global::() - .0 - .entry(group) - .or_default() - .push(bounds); - } + 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::() + .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); + 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); - }); + // 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.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::() - .0 - .get_mut(group) - .unwrap() - .pop(); - } + if let Some(group) = this.group.as_ref() { + cx.default_global::() + .0 + .get_mut(group) + .unwrap() + .pop(); + } + }, + ) }) } } -impl IntoAnyElement for Div +impl IntoAnyElement for Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl ParentElement for Div +impl ParentElement for Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } } -impl Styled for Div +impl Styled for Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { fn style(&mut self) -> &mut StyleRefinement { &mut self.base_style } } -impl Interactive for Div +impl Interactive for Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { - fn listeners(&mut self) -> &mut MouseEventListeners { + fn listeners(&mut self) -> &mut EventListeners { &mut self.listeners } } -impl Hover for Div +impl Hover for Div where + I: ElementIdentity, + F: ElementFocusability, V: 'static + Send + Sync, - K: ElementIdentity, { fn set_hover_style(&mut self, group: Option, style: StyleRefinement) { if let Some(group) = group { @@ -464,10 +524,16 @@ where } } -impl Click for Div where V: 'static + Send + Sync {} - -impl Active for Div +impl Click for Div where + F: ElementFocusability, + V: 'static + Send + Sync, +{ +} + +impl Active for Div +where + F: ElementFocusability, V: 'static + Send + Sync, { fn set_active_style(&mut self, group: Option, style: StyleRefinement) { diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index e7775fc434..e1e93fdd94 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/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 { - base: Div, +pub struct Img { + base: Div, uri: Option, grayscale: bool, } -pub fn img() -> Img +pub fn img() -> Img where V: 'static + Send + Sync, { @@ -39,8 +39,8 @@ where } } -impl Img { - pub fn id(self, id: impl Into) -> Img { +impl Img { + pub fn id(self, id: impl Into) -> Img { 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 { + fn listeners(&mut self) -> &mut EventListeners { self.base.listeners() } } @@ -151,9 +151,9 @@ where } } -impl Click for Img where V: 'static + Send + Sync {} +impl Click for Img where V: 'static + Send + Sync {} -impl Active for Img +impl Active for Img where V: 'static + Send + Sync, { diff --git a/crates/gpui3/src/elements/svg.rs b/crates/gpui3/src/elements/svg.rs index 7904c2c729..aec1164295 100644 --- a/crates/gpui3/src/elements/svg.rs +++ b/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 { - base: Div, +pub struct Svg { + base: Div, path: Option, } -pub fn svg() -> Svg +pub fn svg() -> Svg where V: 'static + Send + Sync, { @@ -31,8 +31,8 @@ where } } -impl Svg { - pub fn id(self, id: impl Into) -> Svg { +impl Svg { + pub fn id(self, id: impl Into) -> Svg { 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 { + fn listeners(&mut self) -> &mut EventListeners { self.base.listeners() } } @@ -125,9 +125,9 @@ where } } -impl Click for Svg where V: 'static + Send + Sync {} +impl Click for Svg where V: 'static + Send + Sync {} -impl Active for Svg +impl Active for Svg where V: 'static + Send + Sync, { diff --git a/crates/gpui3/src/interactive.rs b/crates/gpui3/src/interactive.rs index bc8976439a..35bcab15a2 100644 --- a/crates/gpui3/src/interactive.rs +++ b/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; + fn listeners(&mut self) -> &mut EventListeners; fn on_mouse_down( mut self, @@ -164,43 +164,52 @@ pub trait Click: Interactive { } } -type MouseDownHandler = Arc< +type MouseDownListener = Arc< dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, >; -type MouseUpHandler = Arc< +type MouseUpListener = Arc< dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, >; -type MouseClickHandler = +type MouseClickListener = Arc) + Send + Sync + 'static>; -type MouseMoveHandler = Arc< +type MouseMoveListener = Arc< dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, >; -type ScrollWheelHandler = Arc< + +type ScrollWheelListener = Arc< dyn Fn(&mut V, &ScrollWheelEvent, &Bounds, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, >; -pub struct MouseEventListeners { - pub mouse_down: SmallVec<[MouseDownHandler; 2]>, - pub mouse_up: SmallVec<[MouseUpHandler; 2]>, - pub mouse_click: SmallVec<[MouseClickHandler; 2]>, - pub mouse_move: SmallVec<[MouseMoveHandler; 2]>, - pub scroll_wheel: SmallVec<[ScrollWheelHandler; 2]>, +pub type KeyDownListener = + Arc) + Send + Sync + 'static>; + +pub type KeyUpListener = + Arc) + Send + Sync + 'static>; + +pub struct EventListeners { + pub mouse_down: SmallVec<[MouseDownListener; 2]>, + pub mouse_up: SmallVec<[MouseUpListener; 2]>, + pub mouse_click: SmallVec<[MouseClickListener; 2]>, + pub mouse_move: SmallVec<[MouseMoveListener; 2]>, + pub scroll_wheel: SmallVec<[ScrollWheelListener; 2]>, + pub key_down: SmallVec<[KeyDownListener; 2]>, + pub key_up: SmallVec<[KeyUpListener; 2]>, } -impl Default for MouseEventListeners { +impl Default for EventListeners { fn default() -> Self { Self { mouse_down: SmallVec::new(), @@ -208,6 +217,8 @@ impl Default for MouseEventListeners { mouse_click: SmallVec::new(), mouse_move: SmallVec::new(), scroll_wheel: SmallVec::new(), + key_down: SmallVec::new(), + key_up: SmallVec::new(), } } } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 7ca09e3698..17687f3597 100644 --- a/crates/gpui3/src/window.rs +++ b/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; +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct FocusId(usize); + +#[derive(Clone)] +pub struct FocusHandle { + id: FocusId, +} + pub struct Window { handle: AnyWindowHandle, platform_window: MainThreadOnly>, @@ -57,11 +65,18 @@ pub struct Window { z_index_stack: StackingOrder, content_mask_stack: Vec>, mouse_event_handlers: HashMap>, + key_down_listener_stack: + Vec>, + key_up_listener_stack: + Vec>, propagate_event: bool, mouse_position: Point, scale_factor: f32, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, + focus: Option, + 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( &mut self, f: impl FnOnce(&mut MainThread>) -> 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 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 { + pub fn handle(&self) -> WeakHandle { 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) + Send + 'static) { + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 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( &mut self, handle: &Handle, - on_notify: impl Fn(&mut S, Handle, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static, + on_notify: impl Fn(&mut V, Handle, &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( &mut self, handle: &Handle, - on_event: impl Fn(&mut S, Handle, &E::Event, &mut ViewContext<'_, '_, S>) + on_event: impl Fn(&mut V, Handle, &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( &mut self, handle: &Handle, - 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( + &mut self, + focus_handle: Option, + key_down: impl IntoIterator>, + key_up: impl IntoIterator>, + 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( &mut self, - view: &mut S, - f: impl FnOnce(&mut S, &mut MainThread>) -> R + Send + 'static, + view: &mut V, + f: impl FnOnce(&mut V, &mut MainThread>) -> R + Send + 'static, ) -> Task> where R: Send + 'static, @@ -1021,7 +1105,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { pub fn spawn( &mut self, - f: impl FnOnce(WeakHandle, AsyncWindowContext) -> Fut + Send + 'static, + f: impl FnOnce(WeakHandle, AsyncWindowContext) -> Fut + Send + 'static, ) -> Task where R: Send + 'static, @@ -1036,7 +1120,7 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { pub fn on_mouse_event( &mut self, - handler: impl Fn(&mut S, &Event, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, ) { let handle = self.handle().upgrade(self).unwrap(); self.window_cx.on_mouse_event(move |event, phase, cx| {