Merge pull request #2134 from zed-industries/fix-action-keystroke-bugs

Fix several action dispatching bugs
This commit is contained in:
Mikayla Maki 2023-02-08 15:56:50 -08:00 committed by GitHub
commit 767d2f9766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 50 deletions

View File

@ -63,6 +63,7 @@ pub struct ContextMenu {
visible: bool, visible: bool,
previously_focused_view_id: Option<usize>, previously_focused_view_id: Option<usize>,
clicked: bool, clicked: bool,
parent_view_id: usize,
_actions_observation: Subscription, _actions_observation: Subscription,
} }
@ -114,6 +115,8 @@ impl View for ContextMenu {
impl ContextMenu { impl ContextMenu {
pub fn new(cx: &mut ViewContext<Self>) -> Self { pub fn new(cx: &mut ViewContext<Self>) -> Self {
let parent_view_id = cx.parent().unwrap();
Self { Self {
show_count: 0, show_count: 0,
anchor_position: Default::default(), anchor_position: Default::default(),
@ -123,6 +126,7 @@ impl ContextMenu {
visible: Default::default(), visible: Default::default(),
previously_focused_view_id: Default::default(), previously_focused_view_id: Default::default(),
clicked: false, clicked: false,
parent_view_id,
_actions_observation: cx.observe_actions(Self::action_dispatched), _actions_observation: cx.observe_actions(Self::action_dispatched),
} }
} }
@ -251,6 +255,7 @@ impl ContextMenu {
} }
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element { fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
let window_id = cx.window_id();
let style = cx.global::<Settings>().theme.context_menu.clone(); let style = cx.global::<Settings>().theme.context_menu.clone();
Flex::row() Flex::row()
.with_child( .with_child(
@ -289,6 +294,8 @@ impl ContextMenu {
Some(ix) == self.selected_index, Some(ix) == self.selected_index,
); );
KeystrokeLabel::new( KeystrokeLabel::new(
window_id,
self.parent_view_id,
action.boxed_clone(), action.boxed_clone(),
style.keystroke.container, style.keystroke.container,
style.keystroke.text.clone(), style.keystroke.text.clone(),
@ -318,6 +325,7 @@ impl ContextMenu {
let style = cx.global::<Settings>().theme.context_menu.clone(); let style = cx.global::<Settings>().theme.context_menu.clone();
let window_id = cx.window_id();
MouseEventHandler::<Menu>::new(0, cx, |_, cx| { MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
Flex::column() Flex::column()
.with_children(self.items.iter().enumerate().map(|(ix, item)| { .with_children(self.items.iter().enumerate().map(|(ix, item)| {
@ -337,6 +345,8 @@ impl ContextMenu {
) )
.with_child({ .with_child({
KeystrokeLabel::new( KeystrokeLabel::new(
window_id,
self.parent_view_id,
action.boxed_clone(), action.boxed_clone(),
style.keystroke.container, style.keystroke.container,
style.keystroke.text.clone(), style.keystroke.text.clone(),

View File

@ -1332,6 +1332,31 @@ impl MutableAppContext {
self.action_deserializers.keys().copied() self.action_deserializers.keys().copied()
} }
/// Return keystrokes that would dispatch the given action on the given view.
pub(crate) fn keystrokes_for_action(
&mut self,
window_id: usize,
view_id: usize,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
let mut contexts = Vec::new();
for view_id in self.ancestors(window_id, view_id) {
if let Some(view) = self.views.get(&(window_id, view_id)) {
contexts.push(view.keymap_context(self));
}
}
self.keystroke_matcher
.bindings_for_action_type(action.as_any().type_id())
.find_map(|b| {
if b.match_context(&contexts) {
b.keystrokes().map(|s| s.into())
} else {
None
}
})
}
pub fn available_actions( pub fn available_actions(
&self, &self,
window_id: usize, window_id: usize,
@ -1339,8 +1364,10 @@ impl MutableAppContext {
) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> { ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect(); let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect();
let mut contexts = Vec::new();
for view_id in self.ancestors(window_id, view_id) { for view_id in self.ancestors(window_id, view_id) {
if let Some(view) = self.views.get(&(window_id, view_id)) { if let Some(view) = self.views.get(&(window_id, view_id)) {
contexts.push(view.keymap_context(self));
let view_type = view.as_any().type_id(); let view_type = view.as_any().type_id();
if let Some(actions) = self.actions.get(&view_type) { if let Some(actions) = self.actions.get(&view_type) {
action_types.extend(actions.keys().copied()); action_types.extend(actions.keys().copied());
@ -1357,6 +1384,7 @@ impl MutableAppContext {
deserialize("{}").ok()?, deserialize("{}").ok()?,
self.keystroke_matcher self.keystroke_matcher
.bindings_for_action_type(*type_id) .bindings_for_action_type(*type_id)
.filter(|b| b.match_context(&contexts))
.collect(), .collect(),
)) ))
} else { } else {
@ -1384,34 +1412,6 @@ impl MutableAppContext {
self.global_actions.contains_key(&action_type) self.global_actions.contains_key(&action_type)
} }
/// Return keystrokes that would dispatch the given action closest to the focused view, if there are any.
pub(crate) fn keystrokes_for_action(
&mut self,
window_id: usize,
view_stack: &[usize],
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keystroke_matcher.contexts.clear();
for view_id in view_stack.iter().rev() {
let view = self
.cx
.views
.get(&(window_id, *view_id))
.expect("view in responder chain does not exist");
self.keystroke_matcher
.contexts
.push(view.keymap_context(self.as_ref()));
let keystrokes = self
.keystroke_matcher
.keystrokes_for_action(action, &self.keystroke_matcher.contexts);
if keystrokes.is_some() {
return keystrokes;
}
}
None
}
// Traverses the parent tree. Walks down the tree toward the passed // Traverses the parent tree. Walks down the tree toward the passed
// view calling visit with true. Then walks back up the tree calling visit with false. // view calling visit with true. Then walks back up the tree calling visit with false.
// If `visit` returns false this function will immediately return. // If `visit` returns false this function will immediately return.
@ -1915,10 +1915,11 @@ impl MutableAppContext {
{ {
self.update(|this| { self.update(|this| {
let view_id = post_inc(&mut this.next_entity_id); let view_id = post_inc(&mut this.next_entity_id);
// Make sure we can tell child views about their parent
this.cx.parents.insert((window_id, view_id), parent_id);
let mut cx = ViewContext::new(this, window_id, view_id); let mut cx = ViewContext::new(this, window_id, view_id);
let handle = if let Some(view) = build_view(&mut cx) { let handle = if let Some(view) = build_view(&mut cx) {
this.cx.views.insert((window_id, view_id), Box::new(view)); this.cx.views.insert((window_id, view_id), Box::new(view));
this.cx.parents.insert((window_id, view_id), parent_id);
if let Some(window) = this.cx.windows.get_mut(&window_id) { if let Some(window) = this.cx.windows.get_mut(&window_id) {
window window
.invalidation .invalidation
@ -1928,6 +1929,7 @@ impl MutableAppContext {
} }
Some(ViewHandle::new(window_id, view_id, &this.cx.ref_counts)) Some(ViewHandle::new(window_id, view_id, &this.cx.ref_counts))
} else { } else {
this.cx.parents.remove(&(window_id, view_id));
None None
}; };
handle handle
@ -2813,6 +2815,16 @@ impl AppContext {
})) }))
} }
/// Returns the id of the parent of the given view, or none if the given
/// view is the root.
fn parent(&self, window_id: usize, view_id: usize) -> Option<usize> {
if let Some(ParentId::View(view_id)) = self.parents.get(&(window_id, view_id)) {
Some(*view_id)
} else {
None
}
}
pub fn is_child_focused(&self, view: impl Into<AnyViewHandle>) -> bool { pub fn is_child_focused(&self, view: impl Into<AnyViewHandle>) -> bool {
let view = view.into(); let view = view.into();
if let Some(focused_view_id) = self.focused_view_id(view.window_id) { if let Some(focused_view_id) = self.focused_view_id(view.window_id) {
@ -3852,6 +3864,10 @@ impl<'a, T: View> ViewContext<'a, T> {
.build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view) .build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view)
} }
pub fn parent(&mut self) -> Option<usize> {
self.cx.parent(self.window_id, self.view_id)
}
pub fn reparent(&mut self, view_handle: impl Into<AnyViewHandle>) { pub fn reparent(&mut self, view_handle: impl Into<AnyViewHandle>) {
let view_handle = view_handle.into(); let view_handle = view_handle.into();
if self.window_id != view_handle.window_id { if self.window_id != view_handle.window_id {

View File

@ -12,15 +12,21 @@ pub struct KeystrokeLabel {
action: Box<dyn Action>, action: Box<dyn Action>,
container_style: ContainerStyle, container_style: ContainerStyle,
text_style: TextStyle, text_style: TextStyle,
window_id: usize,
view_id: usize,
} }
impl KeystrokeLabel { impl KeystrokeLabel {
pub fn new( pub fn new(
window_id: usize,
view_id: usize,
action: Box<dyn Action>, action: Box<dyn Action>,
container_style: ContainerStyle, container_style: ContainerStyle,
text_style: TextStyle, text_style: TextStyle,
) -> Self { ) -> Self {
Self { Self {
window_id,
view_id,
action, action,
container_style, container_style,
text_style, text_style,
@ -37,7 +43,10 @@ impl Element for KeystrokeLabel {
constraint: SizeConstraint, constraint: SizeConstraint,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> (Vector2F, ElementBox) { ) -> (Vector2F, ElementBox) {
let mut element = if let Some(keystrokes) = cx.keystrokes_for_action(self.action.as_ref()) { let mut element = if let Some(keystrokes) =
cx.app
.keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
{
Flex::row() Flex::row()
.with_children(keystrokes.iter().map(|keystroke| { .with_children(keystrokes.iter().map(|keystroke| {
Label::new(keystroke.to_string(), self.text_style.clone()) Label::new(keystroke.to_string(), self.text_style.clone())

View File

@ -61,11 +61,14 @@ impl Tooltip {
) -> Self { ) -> Self {
struct ElementState<Tag>(Tag); struct ElementState<Tag>(Tag);
struct MouseEventHandlerState<Tag>(Tag); struct MouseEventHandlerState<Tag>(Tag);
let focused_view_id = cx.focused_view_id(cx.window_id).unwrap();
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id); let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
let state = state_handle.read(cx).clone(); let state = state_handle.read(cx).clone();
let tooltip = if state.visible.get() { let tooltip = if state.visible.get() {
let mut collapsed_tooltip = Self::render_tooltip( let mut collapsed_tooltip = Self::render_tooltip(
cx.window_id,
focused_view_id,
text.clone(), text.clone(),
style.clone(), style.clone(),
action.as_ref().map(|a| a.boxed_clone()), action.as_ref().map(|a| a.boxed_clone()),
@ -74,7 +77,7 @@ impl Tooltip {
.boxed(); .boxed();
Some( Some(
Overlay::new( Overlay::new(
Self::render_tooltip(text, style, action, false) Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false)
.constrained() .constrained()
.dynamically(move |constraint, cx| { .dynamically(move |constraint, cx| {
SizeConstraint::strict_along( SizeConstraint::strict_along(
@ -128,6 +131,8 @@ impl Tooltip {
} }
pub fn render_tooltip( pub fn render_tooltip(
window_id: usize,
focused_view_id: usize,
text: String, text: String,
style: TooltipStyle, style: TooltipStyle,
action: Option<Box<dyn Action>>, action: Option<Box<dyn Action>>,
@ -145,8 +150,13 @@ impl Tooltip {
} }
}) })
.with_children(action.map(|action| { .with_children(action.map(|action| {
let keystroke_label = let keystroke_label = KeystrokeLabel::new(
KeystrokeLabel::new(action, style.keystroke.container, style.keystroke.text); window_id,
focused_view_id,
action,
style.keystroke.container,
style.keystroke.text,
);
if measure { if measure {
keystroke_label.boxed() keystroke_label.boxed()
} else { } else {

View File

@ -41,7 +41,7 @@ impl Binding {
}) })
} }
fn match_context(&self, contexts: &[KeymapContext]) -> bool { pub fn match_context(&self, contexts: &[KeymapContext]) -> bool {
self.context_predicate self.context_predicate
.as_ref() .as_ref()
.map(|predicate| predicate.eval(contexts)) .map(|predicate| predicate.eval(contexts))

View File

@ -43,7 +43,7 @@ impl KeymapContextPredicate {
pub fn eval(&self, contexts: &[KeymapContext]) -> bool { pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
let Some(context) = contexts.first() else { return false }; let Some(context) = contexts.first() else { return false };
match self { match self {
Self::Identifier(name) => context.set.contains(name.as_str()), Self::Identifier(name) => (&context.set).contains(name.as_str()),
Self::Equal(left, right) => context Self::Equal(left, right) => context
.map .map
.get(left) .get(left)

View File

@ -14,12 +14,12 @@ use pathfinder_geometry::{
pub trait Vector2FExt { pub trait Vector2FExt {
/// Converts self to an NSPoint with y axis pointing up. /// Converts self to an NSPoint with y axis pointing up.
fn to_screen_ns_point(&self, native_window: id) -> NSPoint; fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint;
} }
impl Vector2FExt for Vector2F { impl Vector2FExt for Vector2F {
fn to_screen_ns_point(&self, native_window: id) -> NSPoint { fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint {
unsafe { unsafe {
let point = NSPoint::new(self.x() as f64, -self.y() as f64); let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64);
msg_send![native_window, convertPointToScreen: point] msg_send![native_window, convertPointToScreen: point]
} }
} }

View File

@ -483,6 +483,7 @@ impl Window {
let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view); let native_view = NSView::init(native_view);
assert!(!native_view.is_null()); assert!(!native_view.is_null());
let window = Self(Rc::new(RefCell::new(WindowState { let window = Self(Rc::new(RefCell::new(WindowState {
@ -828,12 +829,14 @@ impl platform::Window for Window {
let self_id = self_borrow.id; let self_id = self_borrow.id;
unsafe { unsafe {
let window_frame = self_borrow.frame();
let app = NSApplication::sharedApplication(nil); let app = NSApplication::sharedApplication(nil);
// Convert back to screen coordinates // Convert back to screen coordinates
let screen_point = let screen_point = position.to_screen_ns_point(
(position + window_frame.origin()).to_screen_ns_point(self_borrow.native_window); self_borrow.native_window,
self_borrow.content_size().y() as f64,
);
let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];

View File

@ -4,7 +4,6 @@ use crate::{
font_cache::FontCache, font_cache::FontCache,
geometry::rect::RectF, geometry::rect::RectF,
json::{self, ToJson}, json::{self, ToJson},
keymap_matcher::Keystroke,
platform::{CursorStyle, Event}, platform::{CursorStyle, Event},
scene::{ scene::{
CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
@ -604,14 +603,6 @@ pub struct LayoutContext<'a> {
} }
impl<'a> LayoutContext<'a> { impl<'a> LayoutContext<'a> {
pub(crate) fn keystrokes_for_action(
&mut self,
action: &dyn Action,
) -> Option<SmallVec<[Keystroke; 2]>> {
self.app
.keystrokes_for_action(self.window_id, &self.view_stack, action)
}
fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
let print_error = |view_id| { let print_error = |view_id| {
format!( format!(