Navigation · Tabs on_context: a right click on a tab reports that tab and the pointer, not the chosen one

This commit is contained in:
ZacharyZhang-NY committed 2026-10-04 15:53:30 -04:00
1 parent 64293a81af
commit 9977785de6
2 files changed
+63 -1

No files matched your search

+25 -1
View File
@@ -2,7 +2,7 @@ use std::rc::Rc;
use gpui::{
Animation, AnimationExt, AnyElement, App, ElementId, FontWeight, InteractiveElement,
IntoElement, MouseButton, ParentElement, RenderOnce, Role, SharedString,
IntoElement, MouseButton, ParentElement, Pixels, Point, RenderOnce, Role, SharedString,
StatefulInteractiveElement, Styled, Window, div, prelude::*,
};
@@ -23,6 +23,7 @@ pub enum TabPlacement {
}
type OnValue = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
type OnContext = Rc<dyn Fn(&SharedString, Point<Pixels>, &mut Window, &mut App)>;
/// A strip of tabs and the chosen tab's panel. A line slides to the chosen tab; arrows move it.
#[derive(IntoElement)]
@@ -33,6 +34,7 @@ pub struct Tabs {
placement: TabPlacement,
panel: Option<AnyElement>,
on_change: Option<OnValue>,
on_context: Option<OnContext>,
}
impl Tabs {
@@ -48,6 +50,7 @@ impl Tabs {
placement: TabPlacement::default(),
panel: None,
on_change: None,
on_context: None,
}
}
@@ -71,6 +74,17 @@ impl Tabs {
}
}
impl Tabs {
/// A right click on a tab: its value and the pointer.
pub fn on_context(
mut self,
handler: impl Fn(&SharedString, Point<Pixels>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_context = Some(Rc::new(handler));
self
}
}
impl RenderOnce for Tabs {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let values: Vec<SharedString> = self.tabs.iter().map(|tab| tab.value.clone()).collect();
@@ -148,6 +162,8 @@ impl RenderOnce for Tabs {
(false, false) => colors.fg_muted,
};
let pick = pick.clone();
let context = self.on_context.clone().filter(|_| !tab.disabled);
let value = tab.value.clone();
div()
.id(("tab", ix))
.role(Role::Tab)
@@ -185,6 +201,14 @@ impl RenderOnce for Tabs {
}
})
})
.when_some(context, |item, context| {
item.on_mouse_down(MouseButton::Right, move |event, window, cx| {
window.prevent_default();
cx.stop_propagation();
log::info!("tabs: context on {value}");
context(&value, event.position, window, cx);
})
})
.when_some(tab.icon, |item, icon| {
item.child(Icon::new(icon).size(IconSize::Sm).color(fg))
})
+38
View File
@@ -470,3 +470,41 @@ fn steps_fill_a_column_their_block_measures_by_content(cx: &mut TestAppContext)
"the steps span the card inside its padding"
);
}
struct Context_ {
chosen: SharedString,
asked: Option<SharedString>,
}
impl Render for Context_ {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let tabs = [Choice::new("a", "A"), Choice::new("c", "C")];
Tabs::new("tabs", tabs, self.chosen.clone()).on_context(move |value, _, _, cx| {
view.update(cx, |view, cx| {
view.asked = Some(value.clone());
cx.notify();
})
})
}
}
#[gpui::test]
fn a_right_click_names_its_tab_and_keeps_the_choice(cx: &mut TestAppContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|_, _| Context_ {
chosen: "c".into(),
asked: None,
});
settle(cx);
let at = point(px(12.0), px(16.0));
cx.simulate_mouse_move(at, None, Modifiers::none());
cx.simulate_mouse_down(at, gpui::MouseButton::Right, Modifiers::none());
let (chosen, asked) = view.read_with(cx, |view, _| (view.chosen.clone(), view.asked.clone()));
assert_eq!(
asked.as_deref(),
Some("a"),
"the clicked tab, not the chosen one"
);
assert_eq!(chosen, "c");
}