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:
2 files changed
+63
-1
No files matched your search
+25
-1
@@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt, AnyElement, App, ElementId, FontWeight, InteractiveElement,
|
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::*,
|
StatefulInteractiveElement, Styled, Window, div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ pub enum TabPlacement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OnValue = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
|
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.
|
/// A strip of tabs and the chosen tab's panel. A line slides to the chosen tab; arrows move it.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
@@ -33,6 +34,7 @@ pub struct Tabs {
|
|||||||
placement: TabPlacement,
|
placement: TabPlacement,
|
||||||
panel: Option<AnyElement>,
|
panel: Option<AnyElement>,
|
||||||
on_change: Option<OnValue>,
|
on_change: Option<OnValue>,
|
||||||
|
on_context: Option<OnContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tabs {
|
impl Tabs {
|
||||||
@@ -48,6 +50,7 @@ impl Tabs {
|
|||||||
placement: TabPlacement::default(),
|
placement: TabPlacement::default(),
|
||||||
panel: None,
|
panel: None,
|
||||||
on_change: 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 {
|
impl RenderOnce for Tabs {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let values: Vec<SharedString> = self.tabs.iter().map(|tab| tab.value.clone()).collect();
|
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,
|
(false, false) => colors.fg_muted,
|
||||||
};
|
};
|
||||||
let pick = pick.clone();
|
let pick = pick.clone();
|
||||||
|
let context = self.on_context.clone().filter(|_| !tab.disabled);
|
||||||
|
let value = tab.value.clone();
|
||||||
div()
|
div()
|
||||||
.id(("tab", ix))
|
.id(("tab", ix))
|
||||||
.role(Role::Tab)
|
.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| {
|
.when_some(tab.icon, |item, icon| {
|
||||||
item.child(Icon::new(icon).size(IconSize::Sm).color(fg))
|
item.child(Icon::new(icon).size(IconSize::Sm).color(fg))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
"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");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user