Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/gpui/src/app/test_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,12 @@ impl TestAppContext {
.unwrap();
}

/// dispatches a modifiers changed event
pub fn dispatch_modifiers_changed(&mut self, window: AnyWindowHandle, modifiers: Modifiers) {
self.update_window(window, |_, cx| cx.dispatch_modifiers_changed(modifiers))
.unwrap();
}

/// Returns the `TestWindow` backing the given handle.
pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app
Expand Down
155 changes: 127 additions & 28 deletions crates/gpui/src/elements/div.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,31 +291,15 @@ impl Interactivity {
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) {
self.action_listeners.push((
TypeId::of::<A>(),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Capture {
(listener)(action, cx)
}
}),
));
Self::add_action_listener(&mut self.action_listeners, listener, DispatchPhase::Capture);
}

/// Bind the given callback to an action dispatch during the bubble phase
/// The imperative API equivalent to [`InteractiveElement::on_action`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
self.action_listeners.push((
TypeId::of::<A>(),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
(listener)(action, cx)
}
}),
));
Self::add_action_listener(&mut self.action_listeners, listener, DispatchPhase::Bubble);
}

/// Bind the given callback to an action dispatch, based on a dynamic action parameter
Expand All @@ -329,15 +313,51 @@ impl Interactivity {
action: &dyn Action,
listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
) {
let action = action.boxed_clone();
self.action_listeners.push((
(*action).type_id(),
Box::new(move |_, phase, cx| {
if phase == DispatchPhase::Bubble {
(listener)(&action, cx)
}
}),
));
Self::add_boxed_action_listener(&mut self.action_listeners, action, listener);
}

/// Bind the given callback to an action release dispatch during the capture phase
/// The imperative API equivalent to [`InteractiveElement::capture_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn capture_action_release<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) {
Self::add_action_listener(
&mut self.action_release_listeners,
listener,
DispatchPhase::Capture,
);
}

/// Bind the given callback to an action release dispatch during the bubble phase
/// The imperative API equivalent to [`InteractiveElement::on_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn on_action_release<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) {
Self::add_action_listener(
&mut self.action_release_listeners,
listener,
DispatchPhase::Bubble,
);
}

/// Bind the given callback to an action release dispatch, based on a dynamic action parameter
/// instead of a type parameter. Useful for component libraries that want to expose
/// action bindings to their users.
/// The imperative API equivalent to [`InteractiveElement::on_boxed_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
pub fn on_boxed_action_release(
&mut self,
action: &dyn Action,
listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
) {
Self::add_boxed_action_listener(&mut self.action_release_listeners, action, listener);
}

/// Bind the given callback to key down events during the bubble phase
Expand Down Expand Up @@ -485,6 +505,38 @@ impl Interactivity {
pub fn block_mouse(&mut self) {
self.block_mouse = true;
}

fn add_action_listener<A: Action>(
action_listeners: &mut Vec<(TypeId, ActionListener)>,
listener: impl Fn(&A, &mut WindowContext) + 'static,
dispatch_phase: DispatchPhase,
) {
action_listeners.push((
TypeId::of::<A>(),
Box::new(move |action, phase, cx| {
let action = action.downcast_ref().unwrap();
if phase == dispatch_phase {
(listener)(action, cx)
}
}),
));
}

fn add_boxed_action_listener(
action_listeners: &mut Vec<(TypeId, ActionListener)>,
action: &dyn Action,
listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
) {
let action = action.boxed_clone();
action_listeners.push((
(*action).type_id(),
Box::new(move |_, phase, cx| {
if phase == DispatchPhase::Bubble {
(listener)(&action, cx)
}
}),
));
}
}

/// A trait for elements that want to use the standard GPUI event handlers that don't
Expand Down Expand Up @@ -701,7 +753,7 @@ pub trait InteractiveElement: Sized {
}

/// Capture the given action, before normal action dispatch can fire
/// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]
/// The fluent API equivalent to [`Interactivity::capture_action`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
fn capture_action<A: Action>(
Expand Down Expand Up @@ -736,6 +788,46 @@ pub trait InteractiveElement: Sized {
self
}

/// Capture the given action release, before normal action release dispatch can fire
/// The fluent API equivalent to [`Interactivity::capture_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
fn capture_action_release<A: Action>(
mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) -> Self {
self.interactivity().capture_action_release(listener);
self
}

/// Bind the given callback to an action release dispatch during the bubble phase
/// The fluent API equivalent to [`Interactivity::on_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
fn on_action_release<A: Action>(
mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
) -> Self {
self.interactivity().on_action_release(listener);
self
}

/// Bind the given callback to an action release dispatch, based on a dynamic action parameter
/// instead of a type parameter. Useful for component libraries that want to expose
/// action bindings to their users.
/// The fluent API equivalent to [`Interactivity::on_boxed_action_release`]
///
/// See [`ViewContext::listener`](crate::ViewContext::listener) to get access to a view's state from this callback.
fn on_boxed_action_release(
mut self,
action: &dyn Action,
listener: impl Fn(&Box<dyn Action>, &mut WindowContext) + 'static,
) -> Self {
self.interactivity()
.on_boxed_action_release(action, listener);
self
}

/// Bind the given callback to key down events during the bubble phase
/// The fluent API equivalent to [`Interactivity::on_key_down`]
///
Expand Down Expand Up @@ -1196,6 +1288,7 @@ pub struct Interactivity {
pub(crate) key_down_listeners: Vec<KeyDownListener>,
pub(crate) key_up_listeners: Vec<KeyUpListener>,
pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
pub(crate) action_release_listeners: Vec<(TypeId, ActionListener)>,
pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
pub(crate) can_drop_predicate: Option<CanDropPredicate>,
pub(crate) click_listeners: Vec<ClickListener>,
Expand Down Expand Up @@ -1892,6 +1985,8 @@ impl Interactivity {
let key_down_listeners = mem::take(&mut self.key_down_listeners);
let key_up_listeners = mem::take(&mut self.key_up_listeners);
let action_listeners = mem::take(&mut self.action_listeners);
let action_release_listeners =
mem::take(&mut self.action_release_listeners);
cx.with_key_dispatch(
self.key_context.clone(),
element_state.focus_handle.clone(),
Expand All @@ -1912,6 +2007,10 @@ impl Interactivity {
cx.on_action(action_type, listener)
}

for (action_type, listener) in action_release_listeners {
cx.on_action_release(action_type, listener)
}

f(&style, scroll_offset.unwrap_or_default(), cx)
},
);
Expand Down
Loading