From 738b2ce6c50b3ecb65c715b24b41278f66eb2617 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Nov 2023 18:10:46 +0100 Subject: [PATCH 1/3] Extract a `Frame` struct from `Window` Co-Authored-By: Marshall Co-Authored-By: Nathan Co-Authored-By: Piotr --- crates/gpui2/src/app.rs | 6 +- crates/gpui2/src/scene.rs | 10 +- crates/gpui2/src/window.rs | 238 +++++++++++++++++++------------------ 3 files changed, 136 insertions(+), 118 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index f2ac3a91cfacd1d9713266cc2f3f5aca31694cc8..79f80f474d41bc6f6cdaeeb34a6797d348b674b3 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -519,7 +519,7 @@ impl AppContext { window_handle .update(self, |_, cx| { if cx.window.focus == focused { - let mut listeners = mem::take(&mut cx.window.focus_listeners); + let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); let focused = focused .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); let blurred = cx @@ -535,8 +535,8 @@ impl AppContext { } } - listeners.extend(cx.window.focus_listeners.drain(..)); - cx.window.focus_listeners = listeners; + listeners.extend(cx.window.current_frame.focus_listeners.drain(..)); + cx.window.current_frame.focus_listeners = listeners; } }) .ok(); diff --git a/crates/gpui2/src/scene.rs b/crates/gpui2/src/scene.rs index c176479a67f7f51ae47ae7efada8ee04102dee38..918c3c83516fbe29279760b2e5f247ac60353474 100644 --- a/crates/gpui2/src/scene.rs +++ b/crates/gpui2/src/scene.rs @@ -27,8 +27,8 @@ pub(crate) struct SceneBuilder { polychrome_sprites: Vec, } -impl SceneBuilder { - pub fn new() -> SceneBuilder { +impl Default for SceneBuilder { + fn default() -> Self { SceneBuilder { layers_by_order: BTreeMap::new(), splitter: BspSplitter::new(), @@ -40,6 +40,12 @@ impl SceneBuilder { polychrome_sprites: Vec::new(), } } +} + +impl SceneBuilder { + pub fn new() -> SceneBuilder { + SceneBuilder::default() + } pub fn build(&mut self) -> Scene { // Map each layer id to a float between 0. and 1., with 1. closer to the viewer. diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index e55f0152d88dca97156a952a3499f74c71999fb4..0dae6171d9d79f266cefcaec18b4806c1101a92f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -132,7 +132,12 @@ impl FocusHandle { if self.id == ancestor_id { return true; } else { - ancestor = cx.window.focus_parents_by_child.get(&ancestor_id).copied(); + ancestor = cx + .window + .current_frame + .focus_parents_by_child + .get(&ancestor_id) + .copied(); } } false @@ -175,19 +180,8 @@ pub struct Window { pub(crate) layout_engine: TaffyLayoutEngine, pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, - prev_frame_element_states: HashMap, - element_states: HashMap, - prev_frame_key_matchers: HashMap, - key_matchers: HashMap, - z_index_stack: StackingOrder, - content_mask_stack: Vec>, - element_offset_stack: Vec>, - mouse_listeners: HashMap>, - key_dispatch_stack: Vec, - freeze_key_dispatch_stack: bool, - focus_stack: Vec, - focus_parents_by_child: HashMap, - pub(crate) focus_listeners: Vec, + pub(crate) previous_frame: Frame, + pub(crate) current_frame: Frame, pub(crate) focus_handles: Arc>>, default_prevented: bool, mouse_position: Point, @@ -198,12 +192,27 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, activation_observers: SubscriberSet<(), AnyObserver>, - pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, pub(crate) last_blur: Option>, pub(crate) focus: Option, } +#[derive(Default)] +pub(crate) struct Frame { + element_states: HashMap, + key_matchers: HashMap, + mouse_listeners: HashMap>, + pub(crate) focus_listeners: Vec, + key_dispatch_stack: Vec, + freeze_key_dispatch_stack: bool, + focus_parents_by_child: HashMap, + pub(crate) scene_builder: SceneBuilder, + z_index_stack: StackingOrder, + content_mask_stack: Vec>, + element_offset_stack: Vec>, + focus_stack: Vec, +} + impl Window { pub(crate) fn new( handle: AnyWindowHandle, @@ -270,19 +279,8 @@ impl Window { layout_engine: TaffyLayoutEngine::new(), root_view: None, element_id_stack: GlobalElementId::default(), - prev_frame_element_states: HashMap::default(), - element_states: HashMap::default(), - prev_frame_key_matchers: HashMap::default(), - key_matchers: HashMap::default(), - z_index_stack: StackingOrder(SmallVec::new()), - content_mask_stack: Vec::new(), - element_offset_stack: Vec::new(), - mouse_listeners: HashMap::default(), - key_dispatch_stack: Vec::new(), - freeze_key_dispatch_stack: false, - focus_stack: Vec::new(), - focus_parents_by_child: HashMap::default(), - focus_listeners: Vec::new(), + previous_frame: Frame::default(), + current_frame: Frame::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), default_prevented: true, mouse_position, @@ -293,7 +291,6 @@ impl Window { bounds_observers: SubscriberSet::new(), active: false, activation_observers: SubscriberSet::new(), - scene_builder: SceneBuilder::new(), dirty: true, last_blur: None, focus: None, @@ -667,8 +664,9 @@ impl<'a> WindowContext<'a> { &mut self, handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let order = self.window.z_index_stack.clone(); + let order = self.window.current_frame.z_index_stack.clone(); self.window + .current_frame .mouse_listeners .entry(TypeId::of::()) .or_default() @@ -692,9 +690,9 @@ impl<'a> WindowContext<'a> { /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. pub fn stack(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.z_index_stack.push(z_index); + self.window.current_frame.z_index_stack.push(z_index); let result = f(self); - self.window.z_index_stack.pop(); + self.window.current_frame.z_index_stack.pop(); result } @@ -712,8 +710,8 @@ impl<'a> WindowContext<'a> { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Shadow { order: 0, bounds: shadow_bounds.scale(scale_factor), @@ -740,8 +738,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask(); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Quad { order: 0, bounds: bounds.scale(scale_factor), @@ -761,9 +759,10 @@ impl<'a> WindowContext<'a> { path.content_mask = content_mask; path.color = color.into(); let window = &mut *self.window; - window - .scene_builder - .insert(&window.z_index_stack, path.scale(scale_factor)); + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, + path.scale(scale_factor), + ); } /// Paint an underline into the scene for the current frame at the current z-index. @@ -785,8 +784,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask(); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, Underline { order: 0, bounds: bounds.scale(scale_factor), @@ -839,8 +838,8 @@ impl<'a> WindowContext<'a> { }; let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -890,8 +889,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -932,8 +931,8 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, MonochromeSprite { order: 0, bounds, @@ -968,8 +967,8 @@ impl<'a> WindowContext<'a> { let corner_radii = corner_radii.scale(scale_factor); let window = &mut *self.window; - window.scene_builder.insert( - &window.z_index_stack, + window.current_frame.scene_builder.insert( + &window.current_frame.z_index_stack, PolychromeSprite { order: 0, bounds, @@ -1014,7 +1013,7 @@ impl<'a> WindowContext<'a> { } self.window.root_view = Some(root_view); - let scene = self.window.scene_builder.build(); + let scene = self.window.current_frame.scene_builder.build(); self.window.platform_window.draw(scene); let cursor_style = self @@ -1030,39 +1029,21 @@ impl<'a> WindowContext<'a> { self.window.dirty = false; } + /// Rotate the current frame and the previous frame, then clear the current frame. + /// We repopulate all state in the current frame during each paint. fn start_frame(&mut self) { self.text_system().start_frame(); let window = &mut *self.window; - - // Move the current frame element states to the previous frame. - // The new empty element states map will be populated for any element states we - // reference during the upcoming frame. - mem::swap( - &mut window.element_states, - &mut window.prev_frame_element_states, - ); - window.element_states.clear(); - - // Make the current key matchers the previous, and then clear the current. - // An empty key matcher map will be created for every identified element in the - // upcoming frame. - mem::swap( - &mut window.key_matchers, - &mut window.prev_frame_key_matchers, - ); - window.key_matchers.clear(); - - // Clear mouse event listeners, because elements add new element listeners - // when the upcoming frame is painted. - 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.key_dispatch_stack.clear(); - window.focus_parents_by_child.clear(); - window.freeze_key_dispatch_stack = false; + mem::swap(&mut window.previous_frame, &mut window.current_frame); + let frame = &mut window.current_frame; + frame.element_states.clear(); + frame.key_matchers.clear(); + frame.mouse_listeners.values_mut().for_each(Vec::clear); + frame.focus_listeners.clear(); + frame.key_dispatch_stack.clear(); + frame.focus_parents_by_child.clear(); + frame.freeze_key_dispatch_stack = false; } /// Dispatch a mouse or keyboard event on the window. @@ -1126,6 +1107,7 @@ impl<'a> WindowContext<'a> { if let Some(any_mouse_event) = event.mouse_event() { if let Some(mut handlers) = self .window + .current_frame .mouse_listeners .remove(&any_mouse_event.type_id()) { @@ -1160,18 +1142,20 @@ impl<'a> WindowContext<'a> { // Just in case any handlers added new handlers, which is weird, but possible. handlers.extend( self.window + .current_frame .mouse_listeners .get_mut(&any_mouse_event.type_id()) .into_iter() .flat_map(|handlers| handlers.drain(..)), ); self.window + .current_frame .mouse_listeners .insert(any_mouse_event.type_id(), handlers); } } else if let Some(any_key_event) = event.keyboard_event() { let mut did_handle_action = false; - let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack); + let key_dispatch_stack = mem::take(&mut self.window.current_frame.key_dispatch_stack); let key_event_type = any_key_event.type_id(); let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new(); @@ -1233,7 +1217,7 @@ impl<'a> WindowContext<'a> { } drop(context_stack); - self.window.key_dispatch_stack = key_dispatch_stack; + self.window.current_frame.key_dispatch_stack = key_dispatch_stack; return did_handle_action; } @@ -1249,13 +1233,14 @@ impl<'a> WindowContext<'a> { ) -> KeyMatch { let key_match = self .window + .current_frame .key_matchers .get_mut(element_id) .unwrap() .match_keystroke(keystroke, context_stack); if key_match.is_some() { - for matcher in self.window.key_matchers.values_mut() { + for matcher in self.window.current_frame.key_matchers.values_mut() { matcher.clear_pending(); } } @@ -1515,11 +1500,12 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { window.element_id_stack.push(id.into()); let global_id = window.element_id_stack.clone(); - if window.key_matchers.get(&global_id).is_none() { - window.key_matchers.insert( + if window.current_frame.key_matchers.get(&global_id).is_none() { + window.current_frame.key_matchers.insert( global_id.clone(), window - .prev_frame_key_matchers + .previous_frame + .key_matchers .remove(&global_id) .unwrap_or_else(|| KeyMatcher::new(keymap)), ); @@ -1539,9 +1525,12 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { f: impl FnOnce(&mut Self) -> R, ) -> R { let mask = mask.intersect(&self.content_mask()); - self.window_mut().content_mask_stack.push(mask); + self.window_mut() + .current_frame + .content_mask_stack + .push(mask); let result = f(self); - self.window_mut().content_mask_stack.pop(); + self.window_mut().current_frame.content_mask_stack.pop(); result } @@ -1557,15 +1546,19 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { }; let offset = self.element_offset() + offset; - self.window_mut().element_offset_stack.push(offset); + self.window_mut() + .current_frame + .element_offset_stack + .push(offset); let result = f(self); - self.window_mut().element_offset_stack.pop(); + self.window_mut().current_frame.element_offset_stack.pop(); result } /// Obtain the current element offset. fn element_offset(&self) -> Point { self.window() + .current_frame .element_offset_stack .last() .copied() @@ -1587,9 +1580,15 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { self.with_element_id(id, |global_id, cx| { if let Some(any) = cx .window_mut() + .current_frame .element_states .remove(&global_id) - .or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id)) + .or_else(|| { + cx.window_mut() + .previous_frame + .element_states + .remove(&global_id) + }) { // Using the extra inner option to avoid needing to reallocate a new box. let mut state_box = any @@ -1600,11 +1599,15 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .expect("element state is already on the stack"); let (result, state) = f(Some(state), cx); state_box.replace(state); - cx.window_mut().element_states.insert(global_id, state_box); + cx.window_mut() + .current_frame + .element_states + .insert(global_id, state_box); result } else { let (result, state) = f(None, cx); cx.window_mut() + .current_frame .element_states .insert(global_id, Box::new(Some(state))); result @@ -1632,6 +1635,7 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { /// Obtain the current content mask. fn content_mask(&self) -> ContentMask { self.window() + .current_frame .content_mask_stack .last() .cloned() @@ -1716,9 +1720,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.z_index_stack.push(z_index); + self.window.current_frame.z_index_stack.push(z_index); let result = f(self); - self.window.z_index_stack.pop(); + self.window.current_frame.z_index_stack.pop(); result } @@ -1873,11 +1877,14 @@ impl<'a, V: 'static> ViewContext<'a, V> { listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, ) { let handle = self.view().downgrade(); - self.window.focus_listeners.push(Box::new(move |event, cx| { - handle - .update(cx, |view, cx| listener(view, event, cx)) - .log_err(); - })); + self.window + .current_frame + .focus_listeners + .push(Box::new(move |event, cx| { + handle + .update(cx, |view, cx| listener(view, event, cx)) + .log_err(); + })); } pub fn with_key_listeners( @@ -1885,8 +1892,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { key_listeners: impl IntoIterator)>, f: impl FnOnce(&mut Self) -> R, ) -> R { - let old_stack_len = self.window.key_dispatch_stack.len(); - if !self.window.freeze_key_dispatch_stack { + let old_stack_len = self.window.current_frame.key_dispatch_stack.len(); + if !self.window.current_frame.freeze_key_dispatch_stack { for (event_type, listener) in key_listeners { let handle = self.view().downgrade(); let listener = Box::new( @@ -1902,19 +1909,22 @@ impl<'a, V: 'static> ViewContext<'a, V> { .flatten() }, ); - self.window - .key_dispatch_stack - .push(KeyDispatchStackFrame::Listener { + self.window.current_frame.key_dispatch_stack.push( + KeyDispatchStackFrame::Listener { event_type, listener, - }); + }, + ); } } let result = f(self); - if !self.window.freeze_key_dispatch_stack { - self.window.key_dispatch_stack.truncate(old_stack_len); + if !self.window.current_frame.freeze_key_dispatch_stack { + self.window + .current_frame + .key_dispatch_stack + .truncate(old_stack_len); } result @@ -1929,16 +1939,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { return f(self); } - if !self.window.freeze_key_dispatch_stack { + if !self.window.current_frame.freeze_key_dispatch_stack { self.window + .current_frame .key_dispatch_stack .push(KeyDispatchStackFrame::Context(context)); } let result = f(self); - if !self.window.freeze_key_dispatch_stack { - self.window.key_dispatch_stack.pop(); + if !self.window.previous_frame.freeze_key_dispatch_stack { + self.window.previous_frame.key_dispatch_stack.pop(); } result @@ -1949,20 +1960,21 @@ impl<'a, V: 'static> ViewContext<'a, V> { focus_handle: FocusHandle, f: impl FnOnce(&mut Self) -> R, ) -> R { - if let Some(parent_focus_id) = self.window.focus_stack.last().copied() { + if let Some(parent_focus_id) = self.window.current_frame.focus_stack.last().copied() { self.window + .current_frame .focus_parents_by_child .insert(focus_handle.id, parent_focus_id); } - self.window.focus_stack.push(focus_handle.id); + self.window.current_frame.focus_stack.push(focus_handle.id); if Some(focus_handle.id) == self.window.focus { - self.window.freeze_key_dispatch_stack = true; + self.window.current_frame.freeze_key_dispatch_stack = true; } let result = f(self); - self.window.focus_stack.pop(); + self.window.current_frame.focus_stack.pop(); result } From 2fd8b1f4893dee41ea167ab4ab612711bd0dc8fe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Nov 2023 19:03:57 +0100 Subject: [PATCH 2/3] Fix blinking behavior in editor when receiving/losing focus Co-Authored-By: Marshall --- crates/editor2/src/blink_manager.rs | 4 + crates/editor2/src/editor.rs | 47 ++++++++- crates/gpui2/src/app.rs | 20 +++- crates/gpui2/src/window.rs | 152 ++++++++++++++++++++++++---- 4 files changed, 195 insertions(+), 28 deletions(-) diff --git a/crates/editor2/src/blink_manager.rs b/crates/editor2/src/blink_manager.rs index d25e30f649659c6b379acb71c4e9b274bfbfdc31..0fc748f48abbcaf66e47f9ab86dcfd4855a20e3d 100644 --- a/crates/editor2/src/blink_manager.rs +++ b/crates/editor2/src/blink_manager.rs @@ -85,6 +85,10 @@ impl BlinkManager { } pub fn enable(&mut self, cx: &mut ModelContext) { + if self.enabled { + return; + } + self.enabled = true; // Set cursors as invisible and start blinking: this causes cursors // to be visible during the next render. diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ea76a7b57d9eb6545cd8e94c4369034b2f4b49a5..049d304750fa9d8d3ca3c8d99f89158e567aa1ef 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1920,9 +1920,15 @@ impl Editor { cx, ); + let focus_handle = cx.focus_handle(); + cx.on_focus_in(&focus_handle, Self::handle_focus_in) + .detach(); + cx.on_focus_out(&focus_handle, Self::handle_focus_out) + .detach(); + let mut this = Self { handle: cx.view().downgrade(), - focus_handle: cx.focus_handle(), + focus_handle, buffer: buffer.clone(), display_map: display_map.clone(), selections, @@ -9195,6 +9201,45 @@ impl Editor { pub fn focus(&self, cx: &mut WindowContext) { cx.focus(&self.focus_handle) } + + fn handle_focus_in(&mut self, cx: &mut ViewContext) { + if self.focus_handle.is_focused(cx) { + // todo!() + // let focused_event = EditorFocused(cx.handle()); + // cx.emit_global(focused_event); + cx.emit(Event::Focused); + } + if let Some(rename) = self.pending_rename.as_ref() { + let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone(); + cx.focus(&rename_editor_focus_handle); + } else if self.focus_handle.is_focused(cx) { + self.blink_manager.update(cx, BlinkManager::enable); + self.buffer.update(cx, |buffer, cx| { + buffer.finalize_last_transaction(cx); + if self.leader_peer_id.is_none() { + buffer.set_active_selections( + &self.selections.disjoint_anchors(), + self.selections.line_mode, + self.cursor_shape, + cx, + ); + } + }); + } + } + + fn handle_focus_out(&mut self, cx: &mut ViewContext) { + // todo!() + // let blurred_event = EditorBlurred(cx.handle()); + // cx.emit_global(blurred_event); + self.blink_manager.update(cx, BlinkManager::disable); + self.buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + self.hide_context_menu(cx); + hide_hover(self, cx); + cx.emit(Event::Blurred); + cx.notify(); + } } pub trait CollaborationHub { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 79f80f474d41bc6f6cdaeeb34a6797d348b674b3..b38c403cbca807c09d5019bdb551110d64d117c0 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -518,8 +518,9 @@ impl AppContext { ) { window_handle .update(self, |_, cx| { + // The window might change focus multiple times in an effect cycle. + // We only honor effects for the most recently focused handle. if cx.window.focus == focused { - let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); let focused = focused .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); let blurred = cx @@ -528,15 +529,24 @@ impl AppContext { .take() .unwrap() .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - if focused.is_some() || blurred.is_some() { - let event = FocusEvent { focused, blurred }; - for listener in &listeners { + let focus_changed = focused.is_some() || blurred.is_some(); + let event = FocusEvent { focused, blurred }; + + let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); + if focus_changed { + for listener in &mut listeners { listener(&event, cx); } } - listeners.extend(cx.window.current_frame.focus_listeners.drain(..)); cx.window.current_frame.focus_listeners = listeners; + + if focus_changed { + cx.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, cx)); + } } }) .ok(); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0dae6171d9d79f266cefcaec18b4806c1101a92f..f9a5d59acd8246f04642b7e0fb64d2089f25d35e 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -60,7 +60,7 @@ pub enum DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyListener = Box; +type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( &dyn Any, @@ -71,9 +71,49 @@ type AnyKeyListener = Box< + 'static, >; type AnyFocusListener = Box; +type AnyWindowFocusListener = Box bool + 'static>; slotmap::new_key_type! { pub struct FocusId; } +impl FocusId { + /// Obtains whether the element associated with this handle is currently focused. + pub fn is_focused(&self, cx: &WindowContext) -> bool { + cx.window.focus == Some(*self) + } + + /// Obtains whether the element associated with this handle contains the focused + /// element or is itself focused. + pub fn contains_focused(&self, cx: &WindowContext) -> bool { + cx.focused() + .map_or(false, |focused| self.contains(focused.id, cx)) + } + + /// Obtains whether the element associated with this handle is contained within the + /// focused element or is itself focused. + pub fn within_focused(&self, cx: &WindowContext) -> bool { + let focused = cx.focused(); + focused.map_or(false, |focused| focused.id.contains(*self, cx)) + } + + /// Obtains whether this handle contains the given handle in the most recently rendered frame. + pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool { + let mut ancestor = Some(other); + while let Some(ancestor_id) = ancestor { + if *self == ancestor_id { + return true; + } else { + ancestor = cx + .window + .current_frame + .focus_parents_by_child + .get(&ancestor_id) + .copied(); + } + } + false + } +} + /// A handle which can be used to track and manipulate the focused element in a window. pub struct FocusHandle { pub(crate) id: FocusId, @@ -108,39 +148,24 @@ impl FocusHandle { /// Obtains whether the element associated with this handle is currently focused. pub fn is_focused(&self, cx: &WindowContext) -> bool { - cx.window.focus == Some(self.id) + self.id.is_focused(cx) } /// Obtains whether the element associated with this handle contains the focused /// element or is itself focused. pub fn contains_focused(&self, cx: &WindowContext) -> bool { - cx.focused() - .map_or(false, |focused| self.contains(&focused, cx)) + self.id.contains_focused(cx) } /// Obtains whether the element associated with this handle is contained within the /// focused element or is itself focused. pub fn within_focused(&self, cx: &WindowContext) -> bool { - let focused = cx.focused(); - focused.map_or(false, |focused| focused.contains(self, cx)) + self.id.within_focused(cx) } /// Obtains whether this handle contains the given handle in the most recently rendered frame. 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 - .current_frame - .focus_parents_by_child - .get(&ancestor_id) - .copied(); - } - } - false + self.id.contains(other.id, cx) } } @@ -183,6 +208,7 @@ pub struct Window { pub(crate) previous_frame: Frame, pub(crate) current_frame: Frame, pub(crate) focus_handles: Arc>>, + pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, @@ -282,6 +308,7 @@ impl Window { previous_frame: Frame::default(), current_frame: Frame::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), + focus_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, requested_cursor_style: None, @@ -1116,7 +1143,7 @@ impl<'a> WindowContext<'a> { // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. - for (_, handler) in &handlers { + for (_, handler) in &mut handlers { handler(any_mouse_event, DispatchPhase::Capture, self); if !self.app.propagate_event { break; @@ -1125,7 +1152,7 @@ impl<'a> WindowContext<'a> { // Bubble phase, where most normal handlers do their work. if self.app.propagate_event { - for (_, handler) in handlers.iter().rev() { + for (_, handler) in handlers.iter_mut().rev() { handler(any_mouse_event, DispatchPhase::Bubble, self); if !self.app.propagate_event { break; @@ -1872,6 +1899,87 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) } + /// Register a listener to be called when the given focus handle receives focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a listener to be called when the given focus handle or one of its descendants receives focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus_in( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event + .focused + .as_ref() + .map_or(false, |focused| focus_id.contains(focused.id, cx)) + { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a listener to be called when the given focus handle or one of its descendants loses focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus_out( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event + .blurred + .as_ref() + .map_or(false, |focused| focus_id.contains(focused.id, cx)) + { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a focus listener for the current frame only. It will be cleared + /// on the next frame render. You should use this method only from within elements, + /// and we may want to enforce that better via a different context type. + // todo!() Move this to `FrameContext` to emphasize its individuality? pub fn on_focus_changed( &mut self, listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext) + 'static, From 14b41d657dd26a8842bea59da5d89d6f3ba19d7f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Nov 2023 19:09:01 +0100 Subject: [PATCH 3/3] Introduce `ViewContext::on_blur` Co-Authored-By: Marshall --- crates/gpui2/src/scene.rs | 4 ---- crates/gpui2/src/window.rs | 25 ++++++++++++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/gpui2/src/scene.rs b/crates/gpui2/src/scene.rs index 918c3c83516fbe29279760b2e5f247ac60353474..87e89adfa0e15d18512d2a1a748ea5a212a48489 100644 --- a/crates/gpui2/src/scene.rs +++ b/crates/gpui2/src/scene.rs @@ -43,10 +43,6 @@ impl Default for SceneBuilder { } impl SceneBuilder { - pub fn new() -> SceneBuilder { - SceneBuilder::default() - } - pub fn build(&mut self) -> Scene { // Map each layer id to a float between 0. and 1., with 1. closer to the viewer. let mut layer_z_values = vec![0.; self.layers_by_order.len()]; diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index f9a5d59acd8246f04642b7e0fb64d2089f25d35e..38e7c56eebcc01046b9ebdb230b179e3bd3cfe37 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1949,6 +1949,29 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) } + /// Register a listener to be called when the given focus handle loses focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_blur( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event.blurred.as_ref().map(|blurred| blurred.id) == Some(focus_id) { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + /// Register a listener to be called when the given focus handle or one of its descendants loses focus. /// Unlike [on_focus_changed], returns a subscription and persists until the subscription /// is dropped. @@ -1966,7 +1989,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { if event .blurred .as_ref() - .map_or(false, |focused| focus_id.contains(focused.id, cx)) + .map_or(false, |blurred| focus_id.contains(blurred.id, cx)) { listener(view, cx) }