diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index fdb96902e64bb9c8542ea3b1ae51afe460316dad..59c62f4c17c4b571726d5bb7012d657fd6674265 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -8,9 +8,10 @@ pub use model_context::*; use refineable::Refineable; use crate::{ - current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId, - MainThread, MainThreadOnly, Platform, SubscriberSet, SvgRenderer, Task, TextStyle, - TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, + FocusEvent, FocusHandle, FocusId, LayoutId, MainThread, MainThreadOnly, Platform, + SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, + WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -54,6 +55,7 @@ impl App { this: this.clone(), text_system: Arc::new(TextSystem::new(platform.text_system())), pending_updates: 0, + flushing_effects: false, next_frame_callbacks: Default::default(), platform: MainThreadOnly::new(platform, executor.clone()), executor, @@ -97,6 +99,7 @@ pub struct AppContext { this: Weak>, pub(crate) platform: MainThreadOnly, text_system: Arc, + flushing_effects: bool, pending_updates: usize, pub(crate) next_frame_callbacks: HashMap>, pub(crate) executor: Executor, @@ -119,8 +122,10 @@ impl AppContext { pub(crate) fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); - if self.pending_updates == 1 { + if !self.flushing_effects && self.pending_updates == 1 { + self.flushing_effects = true; self.flush_effects(); + self.flushing_effects = false; } self.pending_updates -= 1; result @@ -158,6 +163,7 @@ impl AppContext { } } Effect::Emit { .. } => self.pending_effects.push_back(effect), + Effect::FocusChanged { .. } => self.pending_effects.push_back(effect), } } @@ -168,6 +174,9 @@ impl AppContext { match effect { Effect::Notify { emitter } => self.apply_notify_effect(emitter), Effect::Emit { emitter, event } => self.apply_emit_effect(emitter, event), + Effect::FocusChanged { window_id, focused } => { + self.apply_focus_changed(window_id, focused) + } } } else { break; @@ -222,6 +231,24 @@ impl AppContext { .retain(&emitter, |handler| handler(&event, self)); } + fn apply_focus_changed(&mut self, window_id: WindowId, focused: Option) { + self.update_window(window_id, |cx| { + if cx.window.focus == focused { + let mut listeners = mem::take(&mut cx.window.focus_listeners); + let focused = focused.map(FocusHandle::new); + let blurred = cx.window.last_blur.unwrap().map(FocusHandle::new); + let event = FocusEvent { focused, blurred }; + for listener in &listeners { + listener(&event, cx); + } + + listeners.extend(cx.window.focus_listeners.drain(..)); + cx.window.focus_listeners = listeners; + } + }) + .ok(); + } + pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext(unsafe { mem::transmute(self.this.clone()) }) } @@ -426,6 +453,10 @@ pub(crate) enum Effect { emitter: EntityId, event: Box, }, + FocusChanged { + window_id: WindowId, + focused: Option, + }, } #[cfg(test)] diff --git a/crates/gpui3/src/app/entity_map.rs b/crates/gpui3/src/app/entity_map.rs index ce111c019debd614eafad6ff4620f10868dbbd9f..a756fe0bc86082b890ac5b4629d1aabae2fc3cb5 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 b9c42721d17c7c24e3842ab5e48bc54edd5424f8..9f5d0078f87b5771ab1a08d08fab96818d2ad4c7 100644 --- a/crates/gpui3/src/element.rs +++ b/crates/gpui3/src/element.rs @@ -1,6 +1,7 @@ -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; +use std::mem; pub trait Element: 'static + Send + Sync + IntoAnyElement { type ViewState: 'static + Send + Sync; @@ -8,17 +9,24 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement { fn id(&self) -> Option; - fn layout( + fn initialize( &mut self, - state: &mut Self::ViewState, + view_state: &mut Self::ViewState, element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState); + ) -> Self::ElementState; + + fn layout( + &mut self, + view_state: &mut Self::ViewState, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId; fn paint( &mut self, bounds: Bounds, - state: &mut Self::ViewState, + view_state: &mut Self::ViewState, element_state: &mut Self::ElementState, cx: &mut ViewContext, ); @@ -31,21 +39,54 @@ 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 AsRef for Focusable { + fn as_ref(&self) -> &FocusHandle { + &self.0 + } +} + +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]>; @@ -70,9 +111,10 @@ pub trait ParentElement: Element { } } -trait ElementObject: 'static + Send + Sync { - fn layout(&mut self, state: &mut S, cx: &mut ViewContext) -> LayoutId; - fn paint(&mut self, state: &mut S, offset: Option>, cx: &mut ViewContext); +trait ElementObject: 'static + Send + Sync { + fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext); + fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId; + fn paint(&mut self, view_state: &mut V, offset: Option>, cx: &mut ViewContext); } struct RenderedElement { @@ -81,17 +123,17 @@ struct RenderedElement { } #[derive(Default)] -enum ElementRenderPhase { +enum ElementRenderPhase { #[default] - Rendered, + Start, + Initialized { + frame_state: Option, + }, LayoutRequested { layout_id: LayoutId, - frame_state: Option, - }, - Painted { - bounds: Bounds, - frame_state: Option, + frame_state: Option, }, + Painted, } /// Internal struct that wraps an element to store Layout and ElementState after the element is rendered. @@ -101,52 +143,57 @@ impl RenderedElement { fn new(element: E) -> Self { RenderedElement { element, - phase: ElementRenderPhase::Rendered, + phase: ElementRenderPhase::Start, } } +} - fn paint_with_element_state( - &mut self, - bounds: Bounds, - view_state: &mut E::ViewState, - frame_state: &mut Option, - cx: &mut ViewContext, - ) { - if let Some(id) = self.element.id() { +impl ElementObject for RenderedElement +where + E: Element, +{ + fn initialize(&mut self, view_state: &mut E::ViewState, cx: &mut ViewContext) { + let frame_state = if let Some(id) = self.element.id() { cx.with_element_state(id, |element_state, cx| { - let mut element_state = element_state.unwrap(); - self.element - .paint(bounds, view_state, &mut element_state, cx); + let element_state = self.element.initialize(view_state, element_state, cx); ((), element_state) }); + None } else { - self.element - .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx); - } + let frame_state = self.element.initialize(view_state, None, cx); + Some(frame_state) + }; + + self.phase = ElementRenderPhase::Initialized { frame_state }; } -} -impl ElementObject for RenderedElement -where - E: Element, - S: 'static + Send + Sync, -{ fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext) -> LayoutId { - let (layout_id, frame_state) = if let Some(id) = self.element.id() { - let layout_id = cx.with_element_state(id, |element_state, cx| { - self.element.layout(state, element_state, cx) - }); - (layout_id, None) - } else { - let (layout_id, frame_state) = self.element.layout(state, None, cx); - (layout_id, Some(frame_state)) + let layout_id; + let mut frame_state; + match mem::take(&mut self.phase) { + ElementRenderPhase::Initialized { + frame_state: initial_frame_state, + } => { + frame_state = initial_frame_state; + if let Some(id) = self.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); + (layout_id, element_state) + }); + } else { + layout_id = self + .element + .layout(state, frame_state.as_mut().unwrap(), cx); + } + } + _ => panic!("must call initialize before layout"), }; self.phase = ElementRenderPhase::LayoutRequested { layout_id, frame_state, }; - layout_id } @@ -156,60 +203,63 @@ where offset: Option>, cx: &mut ViewContext, ) { - self.phase = match std::mem::take(&mut self.phase) { - ElementRenderPhase::Rendered => panic!("must call layout before paint"), - + self.phase = match mem::take(&mut self.phase) { ElementRenderPhase::LayoutRequested { layout_id, mut frame_state, } => { let mut bounds = cx.layout_bounds(layout_id); offset.map(|offset| bounds.origin += offset); - self.paint_with_element_state(bounds, view_state, &mut frame_state, cx); - ElementRenderPhase::Painted { - bounds, - frame_state, + if let Some(id) = self.element.id() { + cx.with_element_state(id, |element_state, cx| { + let mut element_state = element_state.unwrap(); + self.element + .paint(bounds, view_state, &mut element_state, cx); + ((), element_state) + }); + } else { + self.element + .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx); } + ElementRenderPhase::Painted } - ElementRenderPhase::Painted { - bounds, - mut frame_state, - } => { - self.paint_with_element_state(bounds, view_state, &mut frame_state, cx); - ElementRenderPhase::Painted { - bounds, - frame_state, - } - } + _ => panic!("must call layout before paint"), }; } } -pub struct AnyElement(Box>); +pub struct AnyElement(Box>); -impl AnyElement { - pub fn new>(element: E) -> Self { +impl AnyElement { + pub fn new>(element: E) -> Self { AnyElement(Box::new(RenderedElement::new(element))) } -} -impl AnyElement { - pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext) -> LayoutId { - self.0.layout(state, cx) + pub fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext) { + self.0.initialize(view_state, cx); + } + + pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId { + self.0.layout(view_state, cx) } - pub fn paint(&mut self, state: &mut S, offset: Option>, cx: &mut ViewContext) { - self.0.paint(state, offset, cx) + pub fn paint( + &mut self, + view_state: &mut V, + offset: Option>, + cx: &mut ViewContext, + ) { + self.0.paint(view_state, offset, cx) } } -pub trait IntoAnyElement { - fn into_any(self) -> AnyElement; +pub trait IntoAnyElement { + fn into_any(self) -> AnyElement; } -impl IntoAnyElement for AnyElement { - fn into_any(self) -> AnyElement { +impl IntoAnyElement for AnyElement { + fn into_any(self) -> AnyElement { self } } diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index d110d811320e1b462259b3bb4e99209ea1deeec5..c95ca6e853604b823d784d5b7b8bfc97b8178dfe 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -1,15 +1,15 @@ 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, Focus, FocusHandle, Focusable, + Hover, Identified, Interactive, IntoAnyElement, LayoutId, MouseClickEvent, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, NonFocusable, Overflow, ParentElement, Pixels, Point, + ScrollWheelEvent, SharedString, Style, StyleRefinement, Styled, ViewContext, }; use collections::HashMap; use parking_lot::Mutex; use refineable::Refineable; use smallvec::SmallVec; -use std::sync::Arc; +use std::{mem, sync::Arc}; #[derive(Default)] pub struct DivState { @@ -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,20 @@ where group_hover: None, active_style: StyleRefinement::default(), group_active: None, - listeners: MouseEventListeners::default(), + focus_style: StyleRefinement::default(), + focus_in_style: StyleRefinement::default(), + in_focus_style: StyleRefinement::default(), + listeners: EventListeners::default(), } } -pub struct Div { - kind: K, +pub struct Div< + V: 'static + Send + Sync, + I: ElementIdentity = Anonymous, + F: ElementFocusability = NonFocusable, +> { + identity: I, + focusability: F, children: SmallVec<[AnyElement; 2]>, group: Option, base_style: StyleRefinement, @@ -86,7 +95,10 @@ pub struct Div group_hover: Option, active_style: StyleRefinement, group_active: Option, - listeners: MouseEventListeners, + focus_style: StyleRefinement, + focus_in_style: StyleRefinement, + in_focus_style: StyleRefinement, + listeners: EventListeners, } struct GroupStyle { @@ -94,13 +106,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, @@ -108,15 +122,19 @@ where group_hover: self.group_hover, active_style: self.active_style, group_active: self.group_active, + focus_style: self.focus_style, + focus_in_style: self.focus_in_style, + in_focus_style: self.in_focus_style, listeners: self.listeners, } } } -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()); @@ -187,6 +205,20 @@ where let mut computed_style = Style::default(); computed_style.refine(&self.base_style); + if let Some(handle) = self.focusability.focus_handle() { + if handle.contains_focused(cx) { + computed_style.refine(&self.focus_in_style); + } + + if handle.within_focused(cx) { + computed_style.refine(&self.in_focus_style); + } + + if handle.is_focused(cx) { + computed_style.refine(&self.focus_style); + } + } + let mouse_position = cx.mouse_position(); if let Some(group_hover) = self.group_hover.as_ref() { @@ -258,12 +290,12 @@ where } fn paint_event_listeners( - &self, + &mut self, bounds: Bounds, pending_click: Arc>>, cx: &mut ViewContext, ) { - let click_listeners = self.listeners.mouse_click.clone(); + let click_listeners = mem::take(&mut self.listeners.mouse_click); let mouse_down = pending_click.lock().clone(); if let Some(mouse_down) = mouse_down { cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { @@ -287,25 +319,25 @@ where }); } - for listener in self.listeners.mouse_down.iter().cloned() { + for listener in mem::take(&mut self.listeners.mouse_down) { cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.listeners.mouse_up.iter().cloned() { + for listener in mem::take(&mut self.listeners.mouse_up) { cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.listeners.mouse_move.iter().cloned() { + for listener in mem::take(&mut self.listeners.mouse_move) { cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.listeners.scroll_wheel.iter().cloned() { + for listener in mem::take(&mut self.listeners.scroll_wheel) { cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) @@ -313,26 +345,92 @@ 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, + focus_style: self.focus_style, + focus_in_style: self.focus_in_style, + in_focus_style: self.in_focus_style, + listeners: self.listeners, + } + } +} + +impl Focus for Div where + I: ElementIdentity, + V: 'static + Send + Sync, +{ + fn handle(&self) -> &FocusHandle { + self.focusability.as_ref() + } + + fn set_focus_style(&mut self, style: StyleRefinement) { + self.focus_style = style; + } + + fn set_focus_in_style(&mut self, style: StyleRefinement) { + self.focus_in_style = style; + } + + fn set_in_focus_style(&mut self, style: StyleRefinement) { + self.in_focus_style = style; + } +} + +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( + fn initialize( &mut self, view_state: &mut Self::ViewState, element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - let element_state = element_state.unwrap_or_default(); - let style = self.compute_style(Bounds::default(), &element_state, cx); + ) -> Self::ElementState { + cx.with_focus( + self.focusability.focus_handle().cloned(), + mem::take(&mut self.listeners.key_down), + mem::take(&mut self.listeners.key_up), + mem::take(&mut self.listeners.focus), + |cx| { + for child in &mut self.children { + child.initialize(view_state, cx); + } + element_state.unwrap_or_default() + }, + ) + } + + fn layout( + &mut self, + view_state: &mut Self::ViewState, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + let style = self.compute_style(Bounds::default(), element_state, cx); style.apply_text_style(cx, |cx| { self.with_element_id(cx, |this, cx| { let layout_ids = this @@ -340,9 +438,7 @@ where .iter_mut() .map(|child| child.layout(view_state, cx)) .collect::>(); - - let layout_id = cx.request_layout(&style, layout_ids); - (layout_id, element_state) + cx.request_layout(&style, layout_ids) }) }) } @@ -410,50 +506,55 @@ where } } -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 +565,16 @@ where } } -impl Click for Div where V: 'static + Send + Sync {} +impl Click for Div +where + F: ElementFocusability, + V: 'static + Send + Sync, +{ +} -impl Active for Div +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 e7775fc4347d1df49995af5affd06e7cecda4c08..2cd411f0856af226a88b8580ab0bcbd17834fdf3 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -1,18 +1,23 @@ 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, + ElementFocusability, ElementId, ElementIdentity, EventListeners, Focus, Focusable, 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< + V: 'static + Send + Sync, + I: ElementIdentity = Anonymous, + F: ElementFocusability = NonFocusable, +> { + base: Div, uri: Option, grayscale: bool, } -pub fn img() -> Img +pub fn img() -> Img where V: 'static + Send + Sync, { @@ -23,10 +28,11 @@ where } } -impl Img +impl Img where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { pub fn uri(mut self, uri: impl Into) -> Self { self.uri = Some(uri.into()); @@ -39,8 +45,12 @@ where } } -impl Img { - pub fn id(self, id: impl Into) -> Img { +impl Img +where + V: 'static + Send + Sync, + F: ElementFocusability, +{ + pub fn id(self, id: impl Into) -> Img { Img { base: self.base.id(id), uri: self.uri, @@ -49,20 +59,22 @@ impl Img { } } -impl IntoAnyElement for Img +impl IntoAnyElement for Img where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Element for Img +impl Element for Img where V: Send + Sync + 'static, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { type ViewState = V; type ElementState = DivState; @@ -71,24 +83,30 @@ where self.base.id() } - fn layout( + fn initialize( &mut self, - view_state: &mut Self::ViewState, + view_state: &mut V, element_state: Option, + cx: &mut ViewContext, + ) -> Self::ElementState { + self.base.initialize(view_state, element_state, cx) + } + + fn layout( + &mut self, + view_state: &mut V, + element_state: &mut Self::ElementState, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) - where - Self: Sized, - { + ) -> LayoutId { self.base.layout(view_state, element_state, cx) } fn paint( &mut self, bounds: Bounds, - view: &mut Self::ViewState, + view: &mut V, element_state: &mut Self::ElementState, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { cx.stack(0, |cx| { self.base.paint(bounds, view, element_state, cx); @@ -121,43 +139,74 @@ where } } -impl Styled for Img +impl Styled for Img where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn style(&mut self) -> &mut StyleRefinement { self.base.style() } } -impl Interactive for Img +impl Interactive for Img where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { - fn listeners(&mut self) -> &mut MouseEventListeners { + fn listeners(&mut self) -> &mut EventListeners { self.base.listeners() } } -impl Hover for Img +impl Hover for Img where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn set_hover_style(&mut self, group: Option, style: StyleRefinement) { self.base.set_hover_style(group, style); } } -impl Click for Img where V: 'static + Send + Sync {} +impl Click for Img +where + V: 'static + Send + Sync, + F: ElementFocusability, +{ +} -impl Active for Img +impl Active for Img where V: 'static + Send + Sync, + F: ElementFocusability, { fn set_active_style(&mut self, group: Option, style: StyleRefinement) { self.base.set_active_style(group, style) } } + +impl Focus for Img +where + V: 'static + Send + Sync, + I: ElementIdentity, +{ + 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) + } + + fn handle(&self) -> &crate::FocusHandle { + self.base.handle() + } +} diff --git a/crates/gpui3/src/elements/svg.rs b/crates/gpui3/src/elements/svg.rs index 7904c2c7299d05d80ae95a3e52cff7bf2216c87f..17be310457db6a929e1882d52ab0ddf4dea8a292 100644 --- a/crates/gpui3/src/elements/svg.rs +++ b/crates/gpui3/src/elements/svg.rs @@ -1,16 +1,21 @@ 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, ElementFocusability, + ElementId, ElementIdentity, EventListeners, Focus, Focusable, Hover, Identified, Interactive, + IntoAnyElement, LayoutId, NonFocusable, Pixels, SharedString, StyleRefinement, Styled, + ViewContext, }; use util::ResultExt; -pub struct Svg { - base: Div, +pub struct Svg< + V: 'static + Send + Sync, + I: ElementIdentity = Anonymous, + F: ElementFocusability = NonFocusable, +> { + base: Div, path: Option, } -pub fn svg() -> Svg +pub fn svg() -> Svg where V: 'static + Send + Sync, { @@ -20,10 +25,11 @@ where } } -impl Svg +impl Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { pub fn path(mut self, path: impl Into) -> Self { self.path = Some(path.into()); @@ -31,8 +37,12 @@ where } } -impl Svg { - pub fn id(self, id: impl Into) -> Svg { +impl Svg +where + V: 'static + Send + Sync, + F: ElementFocusability, +{ + pub fn id(self, id: impl Into) -> Svg { Svg { base: self.base.id(id), path: self.path, @@ -40,20 +50,22 @@ impl Svg { } } -impl IntoAnyElement for Svg +impl IntoAnyElement for Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Element for Svg +impl Element for Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { type ViewState = V; type ElementState = DivState; @@ -62,16 +74,22 @@ where self.base.id() } - fn layout( + fn initialize( &mut self, - view: &mut V, + view_state: &mut V, element_state: Option, - cx: &mut crate::ViewContext, - ) -> (LayoutId, Self::ElementState) - where - Self: Sized, - { - self.base.layout(view, element_state, cx) + cx: &mut ViewContext, + ) -> Self::ElementState { + self.base.initialize(view_state, element_state, cx) + } + + fn layout( + &mut self, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + self.base.layout(view_state, element_state, cx) } fn paint( @@ -79,7 +97,7 @@ where bounds: Bounds, view: &mut Self::ViewState, element_state: &mut Self::ElementState, - cx: &mut crate::ViewContext, + cx: &mut ViewContext, ) where Self: Sized, { @@ -95,43 +113,74 @@ where } } -impl Styled for Svg +impl Styled for Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn style(&mut self) -> &mut StyleRefinement { self.base.style() } } -impl Interactive for Svg +impl Interactive for Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { - fn listeners(&mut self) -> &mut MouseEventListeners { + fn listeners(&mut self) -> &mut EventListeners { self.base.listeners() } } -impl Hover for Svg +impl Hover for Svg where V: 'static + Send + Sync, - K: ElementIdentity, + I: ElementIdentity, + F: ElementFocusability, { fn set_hover_style(&mut self, group: Option, style: StyleRefinement) { self.base.set_hover_style(group, style); } } -impl Click for Svg where V: 'static + Send + Sync {} +impl Click for Svg +where + V: 'static + Send + Sync, + F: ElementFocusability, +{ +} -impl Active for Svg +impl Active for Svg where V: 'static + Send + Sync, + F: ElementFocusability, { fn set_active_style(&mut self, group: Option, style: StyleRefinement) { self.base.set_active_style(group, style) } } + +impl Focus for Svg +where + V: 'static + Send + Sync, + I: ElementIdentity, +{ + 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) + } + + fn handle(&self) -> &crate::FocusHandle { + self.base.handle() + } +} diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 47203aaa6052f8037e05e5ab57d5d3c18a719ce1..70d642baebc13126c9951f0c76f521a07de61e0f 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -39,31 +39,40 @@ impl IntoAnyElement for String { } } -pub struct Text { +pub struct Text { text: SharedString, - state_type: PhantomData, + state_type: PhantomData, } -impl IntoAnyElement for Text { - fn into_any(self) -> AnyElement { +impl IntoAnyElement for Text { + fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Element for Text { - type ViewState = S; +impl Element for Text { + type ViewState = V; type ElementState = Arc>>; fn id(&self) -> Option { None } + fn initialize( + &mut self, + _view_state: &mut V, + element_state: Option, + _cx: &mut ViewContext, + ) -> Self::ElementState { + element_state.unwrap_or_default() + } + fn layout( &mut self, - _view: &mut S, - _element_state: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + _view: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { let text_system = cx.text_system().clone(); let text_style = cx.text_style(); let font_size = text_style.font_size * cx.rem_size(); @@ -71,7 +80,6 @@ impl Element for Text { .line_height .to_pixels(font_size.into(), cx.rem_size()); let text = self.text.clone(); - let element_state = Arc::new(Mutex::new(None)); let rem_size = cx.rem_size(); let layout_id = cx.request_measured_layout(Default::default(), rem_size, { @@ -102,15 +110,15 @@ impl Element for Text { } }); - (layout_id, element_state) + layout_id } - fn paint<'a>( + fn paint( &mut self, bounds: Bounds, - _: &mut Self::ViewState, + _: &mut V, element_state: &mut Self::ElementState, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { let element_state = element_state.lock(); let element_state = element_state diff --git a/crates/gpui3/src/events.rs b/crates/gpui3/src/events.rs index 52719ae066e509292ea047a908229fedad9feaaa..8adfba37ada3cd9038378a8f421e352433b001ce 100644 --- a/crates/gpui3/src/events.rs +++ b/crates/gpui3/src/events.rs @@ -1,4 +1,7 @@ -use crate::{point, Keystroke, Modifiers, Pixels, Point}; +use crate::{ + point, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point, ViewContext, +}; +use smallvec::SmallVec; use std::{any::Any, ops::Deref}; #[derive(Clone, Debug, Eq, PartialEq)] @@ -26,7 +29,7 @@ impl Deref for ModifiersChangedEvent { } /// The phase of a touch motion event. -/// Based on the winit enum of the same name, +/// Based on the winit enum of the same name. #[derive(Clone, Copy, Debug)] pub enum TouchPhase { Started, @@ -50,6 +53,12 @@ pub struct MouseUpEvent { pub click_count: usize, } +#[derive(Clone, Debug, Default)] +pub struct MouseClickEvent { + pub down: MouseDownEvent, + pub up: MouseUpEvent, +} + #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -155,7 +164,7 @@ impl Deref for MouseExitEvent { } #[derive(Clone, Debug)] -pub enum Event { +pub enum InputEvent { KeyDown(KeyDownEvent), KeyUp(KeyUpEvent), ModifiersChanged(ModifiersChangedEvent), @@ -166,30 +175,112 @@ pub enum Event { ScrollWheel(ScrollWheelEvent), } -impl Event { +impl InputEvent { pub fn position(&self) -> Option> { match self { - Event::KeyDown { .. } => None, - Event::KeyUp { .. } => None, - Event::ModifiersChanged { .. } => None, - Event::MouseDown(event) => Some(event.position), - Event::MouseUp(event) => Some(event.position), - Event::MouseMoved(event) => Some(event.position), - Event::MouseExited(event) => Some(event.position), - Event::ScrollWheel(event) => Some(event.position), + InputEvent::KeyDown { .. } => None, + InputEvent::KeyUp { .. } => None, + InputEvent::ModifiersChanged { .. } => None, + InputEvent::MouseDown(event) => Some(event.position), + InputEvent::MouseUp(event) => Some(event.position), + InputEvent::MouseMoved(event) => Some(event.position), + InputEvent::MouseExited(event) => Some(event.position), + InputEvent::ScrollWheel(event) => Some(event.position), } } pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> { match self { - Event::KeyDown { .. } => None, - Event::KeyUp { .. } => None, - Event::ModifiersChanged { .. } => None, - Event::MouseDown(event) => Some(event), - Event::MouseUp(event) => Some(event), - Event::MouseMoved(event) => Some(event), - Event::MouseExited(event) => Some(event), - Event::ScrollWheel(event) => Some(event), + InputEvent::KeyDown { .. } => None, + InputEvent::KeyUp { .. } => None, + InputEvent::ModifiersChanged { .. } => None, + InputEvent::MouseDown(event) => Some(event), + InputEvent::MouseUp(event) => Some(event), + InputEvent::MouseMoved(event) => Some(event), + InputEvent::MouseExited(event) => Some(event), + InputEvent::ScrollWheel(event) => Some(event), + } + } + + pub fn keyboard_event<'a>(&'a self) -> Option<&'a dyn Any> { + match self { + InputEvent::KeyDown(event) => Some(event), + InputEvent::KeyUp(event) => Some(event), + InputEvent::ModifiersChanged(event) => Some(event), + InputEvent::MouseDown(_) => None, + InputEvent::MouseUp(_) => None, + InputEvent::MouseMoved(_) => None, + InputEvent::MouseExited(_) => None, + InputEvent::ScrollWheel(_) => None, + } + } +} + +pub struct FocusEvent { + pub blurred: Option, + pub focused: Option, +} + +pub type MouseDownListener = Box< + dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, +>; +pub type MouseUpListener = Box< + dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, +>; +pub type MouseClickListener = + Box) + Send + Sync + 'static>; + +pub type MouseMoveListener = Box< + dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, +>; + +pub type ScrollWheelListener = Box< + dyn Fn(&mut V, &ScrollWheelEvent, &Bounds, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, +>; + +pub type KeyDownListener = + Box) + Send + Sync + 'static>; + +pub type KeyUpListener = + Box) + Send + Sync + 'static>; + +pub type FocusListener = + Box) + 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]>, + pub focus: SmallVec<[FocusListener; 2]>, +} + +impl Default for EventListeners { + fn default() -> Self { + Self { + mouse_down: SmallVec::new(), + mouse_up: SmallVec::new(), + mouse_click: SmallVec::new(), + mouse_move: SmallVec::new(), + scroll_wheel: SmallVec::new(), + key_down: SmallVec::new(), + key_up: SmallVec::new(), + focus: SmallVec::new(), } } } diff --git a/crates/gpui3/src/focus.rs b/crates/gpui3/src/focus.rs new file mode 100644 index 0000000000000000000000000000000000000000..d6e9adc6fd5c156d412f8cf5978733beb980a5c0 --- /dev/null +++ b/crates/gpui3/src/focus.rs @@ -0,0 +1,159 @@ +use crate::{ + DispatchPhase, FocusEvent, FocusHandle, Interactive, KeyDownEvent, KeyUpEvent, StyleRefinement, + ViewContext, +}; + +pub trait Focus: Interactive { + 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 handle(&self) -> &FocusHandle; + + 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 Self::ViewState, &FocusEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + let handle = self.handle().clone(); + self.listeners() + .focus + .push(Box::new(move |view, event, cx| { + if event.focused.as_ref() == Some(&handle) { + listener(view, event, cx) + } + })); + self + } + + fn on_blur( + mut self, + listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + let handle = self.handle().clone(); + self.listeners() + .focus + .push(Box::new(move |view, event, cx| { + if event.blurred.as_ref() == Some(&handle) { + listener(view, event, cx) + } + })); + self + } + + fn on_focus_in( + mut self, + listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + let handle = self.handle().clone(); + self.listeners() + .focus + .push(Box::new(move |view, event, cx| { + if event + .focused + .as_ref() + .map_or(false, |focused| focused.contains(&handle, cx)) + { + listener(view, event, cx) + } + })); + self + } + + fn on_focus_out( + mut self, + listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + let handle = self.handle().clone(); + self.listeners() + .focus + .push(Box::new(move |view, event, cx| { + if event + .blurred + .as_ref() + .map_or(false, |blurred| handle.contains(&blurred, cx)) + { + listener(view, event, cx) + } + })); + self + } + + fn on_key_down( + mut self, + listener: impl Fn( + &mut Self::ViewState, + &KeyDownEvent, + DispatchPhase, + &mut ViewContext, + ) + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + self.listeners().key_down.push(Box::new(listener)); + self + } + + fn on_key_up( + mut self, + listener: impl Fn(&mut Self::ViewState, &KeyUpEvent, DispatchPhase, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + self.listeners().key_up.push(Box::new(listener)); + self + } +} diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 8e841e6c2e499f2be38a2d5ddd9466c4cb64f0c7..caf265a60c8df828308c22524dab7d0eaa612e55 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -6,6 +6,7 @@ mod element; mod elements; mod events; mod executor; +mod focus; mod geometry; mod hover; mod image_cache; @@ -31,6 +32,7 @@ pub use element::*; pub use elements::*; pub use events::*; pub use executor::*; +pub use focus::*; pub use geometry::*; pub use gpui3_macros::*; pub use hover::*; diff --git a/crates/gpui3/src/interactive.rs b/crates/gpui3/src/interactive.rs index bc8976439a0e6e8f18915bd6c6c9eb9f873920d5..f328f22cc0606ccc7708a4ab785e479ff7392785 100644 --- a/crates/gpui3/src/interactive.rs +++ b/crates/gpui3/src/interactive.rs @@ -1,13 +1,10 @@ -use smallvec::SmallVec; - use crate::{ - Bounds, DispatchPhase, Element, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, ScrollWheelEvent, ViewContext, + DispatchPhase, Element, EventListeners, MouseButton, MouseClickEvent, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, 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, @@ -22,7 +19,7 @@ pub trait Interactive: Element { { self.listeners() .mouse_down - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && event.button == button && bounds.contains_point(&event.position) @@ -46,7 +43,7 @@ pub trait Interactive: Element { { self.listeners() .mouse_up - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && event.button == button && bounds.contains_point(&event.position) @@ -70,7 +67,7 @@ pub trait Interactive: Element { { self.listeners() .mouse_down - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture && event.button == button && !bounds.contains_point(&event.position) @@ -94,7 +91,7 @@ pub trait Interactive: Element { { self.listeners() .mouse_up - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture && event.button == button && !bounds.contains_point(&event.position) @@ -117,7 +114,7 @@ pub trait Interactive: Element { { self.listeners() .mouse_move - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { handler(view, event, cx); } @@ -137,7 +134,7 @@ pub trait Interactive: Element { { self.listeners() .scroll_wheel - .push(Arc::new(move |view, event, bounds, phase, cx| { + .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { handler(view, event, cx); } @@ -159,60 +156,7 @@ pub trait Click: Interactive { { self.listeners() .mouse_click - .push(Arc::new(move |view, event, cx| handler(view, event, cx))); + .push(Box::new(move |view, event, cx| handler(view, event, cx))); self } } - -type MouseDownHandler = Arc< - dyn Fn(&mut V, &MouseDownEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + Sync - + 'static, ->; -type MouseUpHandler = Arc< - dyn Fn(&mut V, &MouseUpEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + Sync - + 'static, ->; -type MouseClickHandler = - Arc) + Send + Sync + 'static>; - -type MouseMoveHandler = Arc< - dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) - + Send - + Sync - + 'static, ->; -type ScrollWheelHandler = 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]>, -} - -impl Default for MouseEventListeners { - fn default() -> Self { - Self { - mouse_down: SmallVec::new(), - mouse_up: SmallVec::new(), - mouse_click: SmallVec::new(), - mouse_move: SmallVec::new(), - scroll_wheel: SmallVec::new(), - } - } -} - -pub struct MouseClickEvent { - pub down: MouseDownEvent, - pub up: MouseUpEvent, -} diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 8bfdfbccfa216c8bb0278c795da1565bfcb3a2b7..5bb59acae03831bb37da3d72ed704f9c74c8d900 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -5,9 +5,9 @@ mod mac; mod test; use crate::{ - AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun, - GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, - RenderSvgParams, Result, Scene, SharedString, Size, + AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, FontRun, + GlobalPixels, GlyphId, InputEvent, LineLayout, Pixels, Point, RenderGlyphParams, + RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, }; use anyhow::anyhow; use async_task::Runnable; @@ -81,7 +81,7 @@ pub(crate) trait Platform: 'static { fn on_resign_active(&self, callback: Box); fn on_quit(&self, callback: Box); fn on_reopen(&self, callback: Box); - fn on_event(&self, callback: Box bool>); + fn on_event(&self, callback: Box bool>); fn os_name(&self) -> &'static str; fn os_version(&self) -> Result; @@ -141,7 +141,7 @@ pub(crate) trait PlatformWindow { fn minimize(&self); fn zoom(&self); fn toggle_full_screen(&self); - fn on_event(&self, callback: Box bool>); + fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); fn on_resize(&self, callback: Box, f32)>); fn on_fullscreen(&self, callback: Box); diff --git a/crates/gpui3/src/platform/mac/events.rs b/crates/gpui3/src/platform/mac/events.rs index d776d7ab02c3cac07589b29506cf04b7bc43df64..fa8277baf477299d9a9eb6b991c28a5f03ca7ceb 100644 --- a/crates/gpui3/src/platform/mac/events.rs +++ b/crates/gpui3/src/platform/mac/events.rs @@ -1,5 +1,5 @@ use crate::{ - point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, + point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase, }; @@ -84,7 +84,7 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers { } } -impl Event { +impl InputEvent { pub unsafe fn from_native(native_event: id, window_height: Option) -> Option { let event_type = native_event.eventType(); diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index 9feb0a9546bd406839abb5aeb9cb6f61ed053076..28be8f9d2e68504265c362097a9f6cb885c27376 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/crates/gpui3/src/platform/mac/platform.rs @@ -1,6 +1,6 @@ use super::BoolExt; use crate::{ - AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher, + AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Executor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, @@ -153,7 +153,7 @@ pub struct MacPlatformState { resign_active: Option>, reopen: Option>, quit: Option>, - event: Option bool>>, + event: Option bool>>, // menu_command: Option>, // validate_menu_command: Option bool>>, will_open_menu: Option>, @@ -621,7 +621,7 @@ impl Platform for MacPlatform { self.0.lock().reopen = Some(callback); } - fn on_event(&self, callback: Box bool>) { + fn on_event(&self, callback: Box bool>) { self.0.lock().event = Some(callback); } @@ -937,7 +937,7 @@ unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform { extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { - if let Some(event) = Event::from_native(native_event, None) { + if let Some(event) = InputEvent::from_native(native_event, None) { let platform = get_foreground_platform(this); if let Some(callback) = platform.0.lock().event.as_mut() { if !callback(event) { diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 03ce03b34b96ecf90cbd91b8640487a1ca4e6eb3..519eab5b093a1a0e9fbaf6b9446522df689b1f12 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/crates/gpui3/src/platform/mac/window.rs @@ -1,7 +1,7 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ - display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor, - GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, + display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, GlobalPixels, + InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel, @@ -286,7 +286,7 @@ struct MacWindowState { renderer: MetalRenderer, scene_to_render: Option, kind: WindowKind, - event_callback: Option bool>>, + event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option, f32)>>, fullscreen_callback: Option>, @@ -300,7 +300,7 @@ struct MacWindowState { synthetic_drag_counter: usize, last_fresh_keydown: Option, traffic_light_position: Option>, - previous_modifiers_changed_event: Option, + previous_modifiers_changed_event: Option, // State tracking what the IME did after the last request ime_state: ImeState, // Retains the last IME Text @@ -854,7 +854,7 @@ impl PlatformWindow for MacWindow { .detach(); } - fn on_event(&self, callback: Box bool>) { + fn on_input(&self, callback: Box bool>) { self.0.as_ref().lock().event_callback = Some(callback); } @@ -975,9 +975,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut lock = window_state.as_ref().lock(); let window_height = lock.content_size().height; - let event = unsafe { Event::from_native(native_event, Some(window_height)) }; + let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; - if let Some(Event::KeyDown(event)) = event { + if let Some(InputEvent::KeyDown(event)) = event { // For certain keystrokes, macOS will first dispatch a "key equivalent" event. // If that event isn't handled, it will then dispatch a "key down" event. GPUI // makes no distinction between these two types of events, so we need to ignore @@ -1045,13 +1045,13 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: key: ime_text.clone().unwrap(), }, }; - handled = callback(Event::KeyDown(event_with_ime_text)); + handled = callback(InputEvent::KeyDown(event_with_ime_text)); } if !handled { // empty key happens when you type a deadkey in input composition. // (e.g. on a brazillian keyboard typing quote is a deadkey) if !event.keystroke.key.is_empty() { - handled = callback(Event::KeyDown(event)); + handled = callback(InputEvent::KeyDown(event)); } } } @@ -1097,11 +1097,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let is_active = unsafe { lock.native_window.isKeyWindow() == YES }; let window_height = lock.content_size().height; - let event = unsafe { Event::from_native(native_event, Some(window_height)) }; + let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; if let Some(mut event) = event { let synthesized_second_event = match &mut event { - Event::MouseDown( + InputEvent::MouseDown( event @ MouseDownEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, @@ -1118,7 +1118,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { ..*event }; - Some(Event::MouseDown(MouseDownEvent { + Some(InputEvent::MouseDown(MouseDownEvent { button: MouseButton::Right, ..*event })) @@ -1127,7 +1127,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { // Because we map a ctrl-left_down to a right_down -> right_up let's ignore // the ctrl-left_up to avoid having a mismatch in button down/up events if the // user is still holding ctrl when releasing the left mouse button - Event::MouseUp(MouseUpEvent { + InputEvent::MouseUp(MouseUpEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, .. @@ -1140,7 +1140,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { }; match &event { - Event::MouseMoved( + InputEvent::MouseMoved( event @ MouseMoveEvent { pressed_button: Some(_), .. @@ -1157,18 +1157,18 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .detach(); } - Event::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, + InputEvent::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, - Event::MouseUp(MouseUpEvent { + InputEvent::MouseUp(MouseUpEvent { button: MouseButton::Left, .. }) => { lock.synthetic_drag_counter += 1; } - Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { + InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed - if let Some(Event::ModifiersChanged(ModifiersChangedEvent { + if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers: prev_modifiers, })) = &lock.previous_modifiers_changed_event { @@ -1204,7 +1204,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { modifiers: Default::default(), key: ".".into(), }; - let event = Event::KeyDown(KeyDownEvent { + let event = InputEvent::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, }); @@ -1605,7 +1605,7 @@ async fn synthetic_drag( if lock.synthetic_drag_counter == drag_id { if let Some(mut callback) = lock.event_callback.take() { drop(lock); - callback(Event::MouseMoved(event.clone())); + callback(InputEvent::MouseMoved(event.clone())); window_state.lock().event_callback = Some(callback); } } else { diff --git a/crates/gpui3/src/platform/test.rs b/crates/gpui3/src/platform/test.rs index de836f125d11bfc9b38178cf13f35a18578b9d32..4d63bb415af369eb2f1158ed80397e9a7bb7c868 100644 --- a/crates/gpui3/src/platform/test.rs +++ b/crates/gpui3/src/platform/test.rs @@ -125,7 +125,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_event(&self, _callback: Box bool>) { + fn on_event(&self, _callback: Box bool>) { unimplemented!() } diff --git a/crates/gpui3/src/view.rs b/crates/gpui3/src/view.rs index 99e8c58b645ba08e34529b57b672d2bfc50ef8f7..cc9d2d3d27824c18ac1d2011e821a4b85724802b 100644 --- a/crates/gpui3/src/view.rs +++ b/crates/gpui3/src/view.rs @@ -6,12 +6,12 @@ use crate::{ }; use std::{marker::PhantomData, sync::Arc}; -pub struct View { - state: Handle, - render: Arc) -> AnyElement + Send + Sync + 'static>, +pub struct View { + state: Handle, + render: Arc) -> AnyElement + Send + Sync + 'static>, } -impl View { +impl View { pub fn into_any(self) -> AnyView { AnyView { view: Arc::new(Mutex::new(self)), @@ -19,7 +19,7 @@ impl View { } } -impl Clone for View { +impl Clone for View { fn clone(&self) -> Self { Self { state: self.state.clone(), @@ -28,13 +28,13 @@ impl Clone for View { } } -pub fn view( - state: Handle, - render: impl Fn(&mut S, &mut ViewContext) -> E + Send + Sync + 'static, -) -> View +pub fn view( + state: Handle, + render: impl Fn(&mut V, &mut ViewContext) -> E + Send + Sync + 'static, +) -> View where - E: IntoAnyElement, - S: 'static + Send + Sync, + E: IntoAnyElement, + V: 'static + Send + Sync, { View { state, @@ -42,8 +42,8 @@ where } } -impl - IntoAnyElement for View +impl + IntoAnyElement for View { fn into_any(self) -> AnyElement { AnyElement::new(EraseViewState { @@ -53,74 +53,91 @@ impl } } -impl Element for View { +impl Element for View { type ViewState = (); - type ElementState = AnyElement; + type ElementState = AnyElement; fn id(&self) -> Option { Some(ElementId::View(self.state.id)) } - fn layout( + fn initialize( &mut self, - _: &mut Self::ViewState, + _: &mut (), _: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + cx: &mut ViewContext<()>, + ) -> Self::ElementState { self.state.update(cx, |state, cx| { - let mut element = (self.render)(state, cx); - let layout_id = element.layout(state, cx); - (layout_id, element) + let mut any_element = (self.render)(state, cx); + any_element.initialize(state, cx); + any_element }) } + fn layout( + &mut self, + _: &mut (), + element: &mut Self::ElementState, + cx: &mut ViewContext<()>, + ) -> LayoutId { + self.state.update(cx, |state, cx| element.layout(state, cx)) + } + fn paint( &mut self, _: Bounds, - _: &mut Self::ViewState, + _: &mut (), element: &mut Self::ElementState, - cx: &mut ViewContext, + cx: &mut ViewContext<()>, ) { self.state .update(cx, |state, cx| element.paint(state, None, cx)) } } -struct EraseViewState { - view: View, - parent_view_state_type: PhantomData, +struct EraseViewState { + view: View, + parent_view_state_type: PhantomData, } -impl IntoAnyElement - for EraseViewState +impl IntoAnyElement for EraseViewState where - ViewState: 'static + Send + Sync, - ParentViewState: 'static + Send + Sync, + V: 'static + Send + Sync, + ParentV: 'static + Send + Sync, { - fn into_any(self) -> AnyElement { + fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Element for EraseViewState +impl Element for EraseViewState where - ViewState: 'static + Send + Sync, - ParentViewState: 'static + Send + Sync, + V: 'static + Send + Sync, + ParentV: 'static + Send + Sync, { - type ViewState = ParentViewState; + type ViewState = ParentV; type ElementState = AnyBox; fn id(&self) -> Option { Element::id(&self.view) } - fn layout( + fn initialize( &mut self, _: &mut Self::ViewState, _: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - ViewObject::layout(&mut self.view, cx) + ) -> Self::ElementState { + ViewObject::initialize(&mut self.view, cx) + } + + fn layout( + &mut self, + _: &mut Self::ViewState, + element: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + ViewObject::layout(&mut self.view, element, cx) } fn paint( @@ -136,22 +153,31 @@ where trait ViewObject: 'static + Send + Sync { fn entity_id(&self) -> EntityId; - fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, AnyBox); + fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox; + fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId; fn paint(&mut self, bounds: Bounds, element: &mut AnyBox, cx: &mut WindowContext); } -impl ViewObject for View { +impl ViewObject for View { fn entity_id(&self) -> EntityId { self.state.id } - fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, AnyBox) { + fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox { cx.with_element_id(self.entity_id(), |cx| { self.state.update(cx, |state, cx| { - let mut element = (self.render)(state, cx); - let layout_id = element.layout(state, cx); - let element = Box::new(element) as AnyBox; - (layout_id, element) + let mut any_element = Box::new((self.render)(state, cx)); + any_element.initialize(state, cx); + any_element as AnyBox + }) + }) + } + + fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId { + cx.with_element_id(self.entity_id(), |cx| { + self.state.update(cx, |state, cx| { + let element = element.downcast_mut::>().unwrap(); + element.layout(state, cx) }) }) } @@ -159,7 +185,7 @@ impl ViewObject for View { fn paint(&mut self, _: Bounds, element: &mut AnyBox, cx: &mut WindowContext) { cx.with_element_id(self.entity_id(), |cx| { self.state.update(cx, |state, cx| { - let element = element.downcast_mut::>().unwrap(); + let element = element.downcast_mut::>().unwrap(); element.paint(state, None, cx); }); }); @@ -170,11 +196,11 @@ pub struct AnyView { view: Arc>, } -impl IntoAnyElement for AnyView +impl IntoAnyElement for AnyView where - ParentViewState: 'static + Send + Sync, + ParentV: 'static + Send + Sync, { - fn into_any(self) -> AnyElement { + fn into_any(self) -> AnyElement { AnyElement::new(EraseAnyViewState { view: self, parent_view_state_type: PhantomData, @@ -190,13 +216,22 @@ impl Element for AnyView { Some(ElementId::View(self.view.lock().entity_id())) } - fn layout( + fn initialize( &mut self, _: &mut Self::ViewState, _: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - self.view.lock().layout(cx) + ) -> Self::ElementState { + self.view.lock().initialize(cx) + } + + fn layout( + &mut self, + _: &mut Self::ViewState, + element: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + self.view.lock().layout(element, cx) } fn paint( @@ -215,33 +250,42 @@ struct EraseAnyViewState { parent_view_state_type: PhantomData, } -impl IntoAnyElement for EraseAnyViewState +impl IntoAnyElement for EraseAnyViewState where - ParentViewState: 'static + Send + Sync, + ParentV: 'static + Send + Sync, { - fn into_any(self) -> AnyElement { + fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Element for EraseAnyViewState +impl Element for EraseAnyViewState where - ParentViewState: 'static + Send + Sync, + ParentV: 'static + Send + Sync, { - type ViewState = ParentViewState; + type ViewState = ParentV; type ElementState = AnyBox; fn id(&self) -> Option { Element::id(&self.view) } - fn layout( + fn initialize( &mut self, _: &mut Self::ViewState, _: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - self.view.view.lock().layout(cx) + ) -> Self::ElementState { + self.view.view.lock().initialize(cx) + } + + fn layout( + &mut self, + _: &mut Self::ViewState, + element: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + self.view.view.lock().layout(element, cx) } fn paint( diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 7ca09e3698887725b1312badaef9dcc570126c2b..a9c4dcadea09317643efbb33b8fbea742b804928 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,7 +1,8 @@ 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, + EventEmitter, FocusEvent, FocusListener, FontId, GlobalElementId, GlyphId, Handle, Hsla, + ImageData, InputEvent, IsZero, 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, @@ -21,7 +22,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]>); @@ -39,8 +40,55 @@ pub enum DispatchPhase { Capture, } -type MouseEventHandler = - Arc; +type AnyMouseEventListener = + Box; +type AnyKeyboardEventListener = + Box; +type AnyFocusListener = Box; +type AnyKeyDownListener = + Box; +type AnyKeyUpListener = + Box; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct FocusId(usize); + +#[derive(Clone, PartialEq, Eq)] +pub struct FocusHandle { + pub(crate) id: FocusId, +} + +impl FocusHandle { + pub(crate) fn new(id: FocusId) -> Self { + Self { id } + } + + pub fn is_focused(&self, cx: &WindowContext) -> bool { + cx.window.focus == Some(self.id) + } + + pub fn contains_focused(&self, cx: &WindowContext) -> bool { + cx.focused() + .map_or(false, |focused| self.contains(&focused, cx)) + } + + pub fn within_focused(&self, cx: &WindowContext) -> bool { + let focused = cx.focused(); + focused.map_or(false, |focused| focused.contains(self, cx)) + } + + pub(crate) fn contains(&self, other: &Self, cx: &WindowContext) -> bool { + let mut ancestor = Some(other.id); + while let Some(ancestor_id) = ancestor { + if self.id == ancestor_id { + return true; + } else { + ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied(); + } + } + false + } +} pub struct Window { handle: AnyWindowHandle, @@ -56,12 +104,19 @@ pub struct Window { element_states: HashMap, z_index_stack: StackingOrder, content_mask_stack: Vec>, - mouse_event_handlers: HashMap>, + mouse_listeners: HashMap>, + keyboard_listeners: HashMap>, + focus_stack: Vec, + focus_parents_by_child: HashMap, + pub(crate) focus_listeners: Vec, propagate_event: bool, mouse_position: Point, scale_factor: f32, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, + pub(crate) last_blur: Option>, + pub(crate) focus: Option, + next_focus_id: FocusId, } impl Window { @@ -95,7 +150,7 @@ impl Window { } })); - platform_window.on_event({ + platform_window.on_input({ let cx = cx.to_async(); Box::new(move |event| { cx.update_window(handle, |cx| cx.dispatch_event(event)) @@ -120,12 +175,19 @@ impl Window { element_states: HashMap::default(), z_index_stack: StackingOrder(SmallVec::new()), content_mask_stack: Vec::new(), - mouse_event_handlers: HashMap::default(), + mouse_listeners: HashMap::default(), + keyboard_listeners: HashMap::default(), + focus_stack: Vec::new(), + focus_parents_by_child: HashMap::default(), + focus_listeners: Vec::new(), propagate_event: true, mouse_position, scale_factor, scene_builder: SceneBuilder::new(), dirty: true, + last_blur: None, + focus: None, + next_focus_id: FocusId(0), } } } @@ -149,6 +211,12 @@ impl ContentMask { } } +struct FocusStackFrame { + handle: FocusHandle, + key_down_listeners: SmallVec<[AnyKeyDownListener; 2]>, + key_up_listeners: SmallVec<[AnyKeyUpListener; 2]>, +} + pub struct WindowContext<'a, 'w> { app: Reference<'a, AppContext>, pub(crate) window: Reference<'w, Window>, @@ -166,6 +234,43 @@ 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 focused(&self) -> Option { + self.window.focus.map(|id| FocusHandle::new(id)) + } + + pub fn focus(&mut self, handle: &FocusHandle) { + if self.window.last_blur.is_none() { + self.window.last_blur = Some(self.window.focus); + } + + let window_id = self.window.handle.id; + self.window.focus = Some(handle.id); + self.push_effect(Effect::FocusChanged { + window_id, + focused: Some(handle.id), + }); + self.notify(); + } + + pub fn blur(&mut self) { + if self.window.last_blur.is_none() { + self.window.last_blur = Some(self.window.focus); + } + + let window_id = self.window.handle.id; + self.window.focus = None; + self.push_effect(Effect::FocusChanged { + window_id, + focused: None, + }); + self.notify(); + } + pub fn run_on_main( &mut self, f: impl FnOnce(&mut MainThread>) -> R + Send + 'static, @@ -298,17 +403,30 @@ impl<'a, 'w> WindowContext<'a, 'w> { ) { let order = self.window.z_index_stack.clone(); self.window - .mouse_event_handlers + .mouse_listeners .entry(TypeId::of::()) .or_default() .push(( order, - Arc::new(move |event: &dyn Any, phase, cx| { + Box::new(move |event: &dyn Any, phase, cx| { handler(event.downcast_ref().unwrap(), phase, cx) }), )) } + pub fn on_keyboard_event( + &mut self, + handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + Sync + 'static, + ) { + self.window + .keyboard_listeners + .entry(TypeId::of::()) + .or_default() + .push(Box::new(move |event: &dyn Any, phase, cx| { + handler(event.downcast_ref().unwrap(), phase, cx) + })) + } + pub fn mouse_position(&self) -> Point { self.window.mouse_position } @@ -628,7 +746,8 @@ impl<'a, 'w> WindowContext<'a, 'w> { element_state: Option, cx: &mut ViewContext<()>, ) -> AnyBox { - let (layout_id, mut element_state) = root_view.layout(&mut (), element_state, cx); + let mut element_state = root_view.initialize(&mut (), element_state, cx); + let layout_id = root_view.layout(&mut (), &mut element_state, cx); let available_space = cx.window.content_size.map(Into::into); cx.window .layout_engine @@ -645,21 +764,24 @@ 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 - .mouse_event_handlers - .values_mut() - .for_each(Vec::clear); + window.mouse_listeners.values_mut().for_each(Vec::clear); + + // Clear focus state, because we determine what is focused when the new elements + // in the upcoming frame are initialized. + window.focus_listeners.clear(); + window.keyboard_listeners.values_mut().for_each(Vec::clear); + window.focus_parents_by_child.clear(); } fn end_frame(&mut self) { self.text_system().end_frame(); } - fn dispatch_event(&mut self, event: Event) -> bool { + fn dispatch_event(&mut self, event: InputEvent) -> bool { if let Some(any_mouse_event) = event.mouse_event() { if let Some(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() { self.window.mouse_position = *position; @@ -667,7 +789,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { if let Some(mut handlers) = self .window - .mouse_event_handlers + .mouse_listeners .remove(&any_mouse_event.type_id()) { // Because handlers may add other handlers, we sort every time. @@ -698,15 +820,48 @@ impl<'a, 'w> WindowContext<'a, 'w> { // Just in case any handlers added new handlers, which is weird, but possible. handlers.extend( self.window - .mouse_event_handlers + .mouse_listeners .get_mut(&any_mouse_event.type_id()) .into_iter() .flat_map(|handlers| handlers.drain(..)), ); self.window - .mouse_event_handlers + .mouse_listeners .insert(any_mouse_event.type_id(), handlers); } + } else if let Some(any_keyboard_event) = event.keyboard_event() { + if let Some(mut handlers) = self + .window + .keyboard_listeners + .remove(&any_keyboard_event.type_id()) + { + for handler in &handlers { + handler(any_keyboard_event, DispatchPhase::Capture, self); + if !self.window.propagate_event { + break; + } + } + + if self.window.propagate_event { + for handler in handlers.iter().rev() { + handler(any_keyboard_event, DispatchPhase::Bubble, self); + if !self.window.propagate_event { + break; + } + } + } + + handlers.extend( + self.window + .keyboard_listeners + .get_mut(&any_keyboard_event.type_id()) + .into_iter() + .flat_map(|handlers| handlers.drain(..)), + ); + self.window + .keyboard_listeners + .insert(any_keyboard_event.type_id(), handlers); + } } true @@ -882,7 +1037,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 +1046,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 +1057,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 +1067,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 +1091,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 +1118,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 +1134,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 +1157,86 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { }); } + pub fn with_focus( + &mut self, + focus_handle: Option, + key_down: impl IntoIterator>, + key_up: impl IntoIterator>, + focus: impl IntoIterator>, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + let Some(focus_handle) = focus_handle else { + return f(self); + }; + + let handle = self.handle(); + let window = &mut *self.window; + + for listener in focus { + let handle = handle.clone(); + window.focus_listeners.push(Box::new(move |event, cx| { + handle + .update(cx, |view, cx| listener(view, event, cx)) + .log_err(); + })); + } + + let mut focus_stack = mem::take(&mut window.focus_stack); + if let Some(parent_frame) = focus_stack.last() { + window + .focus_parents_by_child + .insert(focus_handle.id, parent_frame.handle.id); + } + + let mut frame = FocusStackFrame { + handle: focus_handle.clone(), + key_down_listeners: SmallVec::new(), + key_up_listeners: SmallVec::new(), + }; + + for listener in key_down { + let handle = handle.clone(); + frame + .key_down_listeners + .push(Box::new(move |event, phase, cx| { + handle + .update(cx, |view, cx| listener(view, event, phase, cx)) + .log_err(); + })); + } + for listener in key_up { + let handle = handle.clone(); + frame + .key_up_listeners + .push(Box::new(move |event, phase, cx| { + handle + .update(cx, |view, cx| listener(view, event, phase, cx)) + .log_err(); + })); + } + focus_stack.push(frame); + + if Some(focus_handle.id) == window.focus { + for focus_frame in &mut focus_stack { + for listener in focus_frame.key_down_listeners.drain(..) { + self.window_cx.on_keyboard_event(listener); + } + for listener in focus_frame.key_up_listeners.drain(..) { + self.window_cx.on_keyboard_event(listener); + } + } + } + + self.window.focus_stack = focus_stack; + let result = f(self); + self.window.focus_stack.pop(); + 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 +1252,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 +1267,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| { @@ -1045,6 +1276,18 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { }) }); } + + pub fn on_keyboard_event( + &mut self, + handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, + ) { + let handle = self.handle().upgrade(self).unwrap(); + self.window_cx.on_keyboard_event(move |event, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, event, phase, cx); + }) + }); + } } impl<'a, 'w, S: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, S> { diff --git a/crates/gpui3_macros/src/derive_element.rs b/crates/gpui3_macros/src/derive_element.rs index 6dcc84751da6f41a578fab3c01fb12d85ac26c41..d7510faf99cf2f21d8d6968e79b15129dfc65e6d 100644 --- a/crates/gpui3_macros/src/derive_element.rs +++ b/crates/gpui3_macros/src/derive_element.rs @@ -57,27 +57,36 @@ pub fn derive_element(input: TokenStream) -> TokenStream { None } - fn layout( + fn initialize( &mut self, view_state: &mut Self::ViewState, - element_state: Option, - cx: &mut gpui3::ViewContext, - ) -> (gpui3::LayoutId, Self::ElementState) { + _: Option, + cx: &mut gpui3::ViewContext + ) -> Self::ElementState { use gpui3::IntoAnyElement; - let mut rendered_element = self.render(view_state, cx).into_any(); - let layout_id = rendered_element.layout(view_state, cx); - (layout_id, rendered_element) + let mut element = self.render(view_state, cx).into_any(); + element.initialize(view_state, cx); + element + } + + fn layout( + &mut self, + view_state: &mut Self::ViewState, + rendered_element: &mut Self::ElementState, + cx: &mut gpui3::ViewContext, + ) -> gpui3::LayoutId { + rendered_element.layout(view_state, cx) } fn paint( &mut self, bounds: gpui3::Bounds, view_state: &mut Self::ViewState, - element_state: &mut Self::ElementState, + rendered_element: &mut Self::ElementState, cx: &mut gpui3::ViewContext, ) { - element_state.paint(view_state, None, cx) + rendered_element.paint(view_state, None, cx) } } }; diff --git a/crates/ui2/src/theme.rs b/crates/ui2/src/theme.rs index 479b81d9468663968928f8af0dbdc1e6723ee4d5..aa84e7c11f1954646879010e264e12481a0376aa 100644 --- a/crates/ui2/src/theme.rs +++ b/crates/ui2/src/theme.rs @@ -164,31 +164,42 @@ impl Element for Themed { None } - fn layout( + fn initialize( &mut self, - state: &mut E::ViewState, + view_state: &mut Self::ViewState, element_state: Option, + cx: &mut ViewContext, + ) -> Self::ElementState { + cx.with_global(self.theme.clone(), |cx| { + self.child.initialize(view_state, element_state, cx) + }) + } + + fn layout( + &mut self, + view_state: &mut E::ViewState, + element_state: &mut Self::ElementState, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) + ) -> LayoutId where Self: Sized, { cx.with_global(self.theme.clone(), |cx| { - self.child.layout(state, element_state, cx) + self.child.layout(view_state, element_state, cx) }) } fn paint( &mut self, bounds: Bounds, - state: &mut Self::ViewState, + view_state: &mut Self::ViewState, frame_state: &mut Self::ElementState, cx: &mut ViewContext, ) where Self: Sized, { cx.with_global(self.theme.clone(), |cx| { - self.child.paint(bounds, state, frame_state, cx); + self.child.paint(bounds, view_state, frame_state, cx); }); } }