Skip to content
Merged
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
9 changes: 7 additions & 2 deletions crates/traverse/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ mod tests {
let manifest_path = dir.join("relays.json");
let (to_host, _host_rx) = async_channel::unbounded::<String>();
let (_host_tx, from_host) = async_channel::unbounded::<String>();
let host = TraverseHost::start(
let mut host = TraverseHost::start(
HostMux::new(to_host, from_host),
HostConfig {
host_name: "Late".into(),
Expand All @@ -1193,7 +1193,12 @@ mod tests {
)
.unwrap();
let loader = host.loader.clone().unwrap();
// The failed startup fetch paces the next attempt; the loop would wait it out.
// The failed startup fetch paces the next attempt; the loop would wait
// it out. The loop is stopped before the stamp is reset: its first
// pass runs as soon as the runtime schedules it, and once unpaced it
// would fetch the written manifest itself, leaving this refresh with
// nothing to return.
host.refresh.take().unwrap().abort();
loader.state().last_attempt_ms = None;
let manifest = block_on(loader.refresh()).expect("the manifest arrived");
block_on(host.shared.apply_manifest(manifest));
Expand Down
4 changes: 2 additions & 2 deletions crates/ui/src/composer/components/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,8 @@ impl Composer {
.xsmall()
.text_color(cx.theme().muted_foreground),
)
.on_click(cx.listener(move |this, _, _, cx| {
cx.stop_propagation();
.on_click(cx.listener(move |this, _, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
this.remove_image(index, cx);
})),
)
Expand Down
8 changes: 4 additions & 4 deletions crates/ui/src/composer/components/pickers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1196,8 +1196,8 @@ fn render_model_row(
muted
}),
)
.on_click(move |_, _, cx| {
cx.stop_propagation();
.on_click(move |_, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
let fav_id = fav_id.clone();
store_fav.update(cx, |store, _cx| store.toggle_favorite_model(fav_id));
// Refresh the open popover so the star + ordering update.
Expand Down Expand Up @@ -1681,8 +1681,8 @@ fn render_fast_mode_bolt(
compact,
)
.debug_selector(|| "traits-fast-mode".into())
.on_click(move |_, _, cx| {
cx.stop_propagation();
.on_click(move |_, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
let value = fast.value(on != Some(true));
store.update(cx, |store, _cx| {
store.set_active_option(fast.option_id.clone(), Some(value));
Expand Down
12 changes: 6 additions & 6 deletions crates/ui/src/composer/components/user_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,8 @@ impl Composer {
} else {
crate::tr!("userinput.answer")
})
.on_click(cx.listener(|this, _, _, cx| {
cx.stop_propagation();
.on_click(cx.listener(|this, _, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
this.ui_async_expanded = !this.ui_async_expanded;
cx.notify();
}));
Expand All @@ -402,8 +402,8 @@ impl Composer {
})
.icon(IconName::Close)
.tooltip(crate::tr!("userinput.dismiss"))
.on_click(cx.listener(move |this, _, _, cx| {
cx.stop_propagation();
.on_click(cx.listener(move |this, _, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
this.ui_dismissed_request_id = Some(request_dismiss.clone());
cx.notify();
}));
Expand Down Expand Up @@ -454,7 +454,7 @@ impl Composer {
.disabled(index == 0)
.tooltip(crate::tr!("userinput.previous"))
.on_click(cx.listener(move |this, _, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
this.ui_go(-1, &questions_previous, window, cx);
})),
)
Expand All @@ -481,7 +481,7 @@ impl Composer {
.disabled(index + 1 >= total)
.tooltip(crate::tr!("userinput.next_question"))
.on_click(cx.listener(move |this, _, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
this.ui_go(1, &questions_next, window, cx);
})),
)
Expand Down
3 changes: 1 addition & 2 deletions crates/ui/src/markdown/image_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ pub(super) fn badge(
.tooltip(move |window, cx| Tooltip::new(tooltip_url.clone()).build(window, cx))
.child(div().min_w_0().truncate().child(label.clone()))
.on_click(move |_, window, cx| {
gpui_base::TextSelection::end(window, cx);
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
let source = view.read(cx).image_source(&url.to_string().into());
crate::attachments::open_image_lightbox(source, label.to_string(), window, cx);
});
Expand Down
3 changes: 1 addition & 2 deletions crates/ui/src/markdown/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,7 @@ impl Element for Inline {
return;
}
if let Some(link) = Self::link_for_position(&layout, &links, event.position) {
gpui_base::TextSelection::end(window, cx);
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
match view.read(cx).resolve_link(&link.url) {
LinkTarget::Web(url) => cx.open_url(&url),
LinkTarget::Local(path) => cx.open_with_system(&path),
Expand Down
3 changes: 1 addition & 2 deletions crates/ui/src/markdown/inline_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ impl InlineFlow {
});
})
.on_click(move |_, window, cx| {
gpui_base::TextSelection::end(window, cx);
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
if let Some(link) = &link {
match view.read(cx).resolve_link(&link.url) {
LinkTarget::Web(url) => cx.open_url(&url),
Expand Down
3 changes: 1 addition & 2 deletions crates/ui/src/markdown/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,7 @@ fn render_paragraph(
});
})
.on_click(move |_, window, cx| {
gpui_base::TextSelection::end(window, cx);
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
if let Some(link) = &link {
match view.read(cx).resolve_link(&link.url) {
LinkTarget::Web(url) => cx.open_url(&url),
Expand Down
63 changes: 62 additions & 1 deletion crates/ui/src/markdown/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ mod tests {

use gpui::{
AppContext as _, Context, Entity, IntoElement, ListAlignment, ListState, Modifiers,
MouseButton, Render, TestAppContext, VisualTestContext, Window, div, point, px,
MouseButton, Render, StatefulInteractiveElement as _, TestAppContext, VisualTestContext,
Window, div, point, px,
};

use super::*;
Expand Down Expand Up @@ -482,6 +483,32 @@ mod tests {
}
}

struct SwallowedClickRoot {
markdown: Entity<MarkdownState>,
}

impl Render for SwallowedClickRoot {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.size_full()
.child(TextSelectionLayer)
.child(
div()
.id("swallowing-button")
.w_full()
.h(px(52.))
.on_click(|_, window, cx| {
crate::widgets::stop_click_propagation(window, cx)
}),
)
.child(
div()
.h(px(40.))
.child(MarkdownView::new(&self.markdown).selectable(true)),
)
}
}

impl Render for CrossViewRoot {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
Expand Down Expand Up @@ -986,6 +1013,40 @@ mod tests {
});
}

#[gpui::test]
fn click_that_stops_propagation_does_not_leave_selection_following_pointer(
cx: &mut TestAppContext,
) {
cx.update(crate::theme::init);
cx.update(crate::markdown::init);
let (_, cx) = cx.add_window_view(|_, cx| SwallowedClickRoot {
markdown: cx.new(|cx| MarkdownState::new("Hello world", cx)),
});
let cx: &mut VisualTestContext = cx;
cx.update(|window, cx| {
let _ = window.draw(cx);
});

cx.simulate_mouse_down(
point(px(10.), px(20.)),
MouseButton::Left,
Modifiers::default(),
);
cx.simulate_mouse_up(
point(px(10.), px(20.)),
MouseButton::Left,
Modifiers::default(),
);
cx.simulate_mouse_move(point(px(80.), px(70.)), None, Modifiers::default());
cx.update(|window, cx| {
let _ = window.draw(cx);
assert!(
gpui_base::TextSelection::selected_text(window, cx).is_empty(),
"the released pointer kept selecting text"
);
});
}

#[gpui::test]
fn cross_view_drag_copies_in_document_order(cx: &mut TestAppContext) {
cx.update(crate::theme::init);
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/orchestrate_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ impl OrchestrateSettingsPanel {
.tooltip(label.clone())
.aria_label(label)
.on_click(cx.listener(move |this, _, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
on_reset(this, window, cx);
}))
.into_any_element()
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/overlay/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ impl Render for Notification {
.xsmall()
.icon(IconName::Close)
.on_click(cx.listener(|this, _, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
this.dismiss(window, cx);
})),
),
Expand Down
2 changes: 1 addition & 1 deletion crates/ui/src/settings_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2654,7 +2654,7 @@ impl SettingsPage {
.on_click(cx.listener(move |this, _, window, cx| {
// Toggle rows are clickable as a whole; a click that lands
// on this button must not also flip the switch.
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
on_reset(this, window, cx);
}))
.into_any_element(),
Expand Down
10 changes: 5 additions & 5 deletions crates/ui/src/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2051,8 +2051,8 @@ impl SessionsSidebar {
.tooltip(|window, cx| {
Tooltip::new(crate::tr!("sidebar.create_thread").into_owned()).build(window, cx)
})
.on_click(cx.listener(move |this, _, _, cx| {
cx.stop_propagation();
.on_click(cx.listener(move |this, _, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
let cwd = plus_cwd.clone();
let project_id = plus_project_id.clone();
this.store.update(cx, |store, cx| {
Expand Down Expand Up @@ -2670,7 +2670,7 @@ impl SessionsSidebar {
Tooltip::new(crate::tr!("sidebar.archive").into_owned()).build(window, cx)
})
.on_click(cx.listener(move |this, _, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
this.archive_thread(&archive_id, &archive_title, window, cx);
}))
.child(
Expand Down Expand Up @@ -3634,8 +3634,8 @@ impl SessionsSidebar {
.gap(px(2.))
.text_size(px(12.))
.text_color(cx.theme().muted_foreground)
.on_click(cx.listener(move |this, _, _, cx| {
cx.stop_propagation();
.on_click(cx.listener(move |this, _, window, cx| {
crate::widgets::stop_click_propagation(window, cx);
this.store.update(cx, |store, _| {
store.set_thread_collapsed(disclosure_id.clone(), !children_collapsed)
});
Expand Down
10 changes: 10 additions & 0 deletions crates/ui/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,13 @@ pub use progress::Progress;
pub use spinner::Spinner;
pub use switch::Switch;
pub use tooltip::Tooltip;

/// Stops a click's propagation after letting the window text selection see
/// the release. gpui-base ends a selection gesture from a bubble-phase
/// MouseUp listener on the root, and a MouseDown anywhere begins one, so a
/// click handler that only stops propagation leaves the selection following
/// the pointer until the next release.
pub(crate) fn stop_click_propagation(window: &mut gpui::Window, cx: &mut gpui::App) {
gpui_base::TextSelection::end(window, cx);
cx.stop_propagation();
}
2 changes: 1 addition & 1 deletion crates/ui/src/widgets/popover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ impl Popover {
let mut root = div()
.id(self.id)
.on_click(move |_, window, cx| {
cx.stop_propagation();
crate::widgets::stop_click_propagation(window, cx);
toggle.update(cx, |state, cx| state.toggle_open(window, cx));
cx.notify(parent);
})
Expand Down
Loading