.
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
use gpui::AppContext as _;
|
||||||
|
use gpui::{
|
||||||
|
AnyView, App, Context, Entity, IntoElement, ParentElement as _, Render, SharedString,
|
||||||
|
Styled as _, Window, div, px,
|
||||||
|
};
|
||||||
|
use gpui_native_theme::ActiveTheme as _;
|
||||||
|
|
||||||
|
/// A small text panel gpui shows next to a hovered element.
|
||||||
|
pub struct Tooltip {
|
||||||
|
text: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tooltip {
|
||||||
|
/// Creates the tooltip view.
|
||||||
|
pub fn new<L>(text: L, cx: &mut App) -> Entity<Self>
|
||||||
|
where
|
||||||
|
L: Into<SharedString>,
|
||||||
|
{
|
||||||
|
cx.new(|_| Self { text: text.into() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `.tooltip(...)` closure for a plain text tooltip.
|
||||||
|
pub fn text<L>(text: L) -> impl Fn(&mut Window, &mut App) -> AnyView + 'static
|
||||||
|
where
|
||||||
|
L: Into<SharedString>,
|
||||||
|
{
|
||||||
|
let text = text.into();
|
||||||
|
move |_, cx| Tooltip::new(text.clone(), cx).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Tooltip {
|
||||||
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let theme = cx.theme();
|
||||||
|
let text_size = theme.sizes.small.text_size;
|
||||||
|
|
||||||
|
div()
|
||||||
|
.px_2()
|
||||||
|
.py_1()
|
||||||
|
.max_w(px(360.))
|
||||||
|
.bg(theme.colors.surface)
|
||||||
|
.border_1()
|
||||||
|
.border_color(theme.colors.border)
|
||||||
|
.rounded(theme.radius.sm)
|
||||||
|
.text_size(text_size)
|
||||||
|
.text_color(theme.colors.surface_foreground)
|
||||||
|
.child(self.text.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
Context, InteractiveElement as _, IntoElement, Modifiers, MouseMoveEvent, ParentElement as _,
|
||||||
|
Render, StatefulInteractiveElement as _, Styled as _, TestAppContext, VisualTestContext,
|
||||||
|
Window, div, point, px,
|
||||||
|
};
|
||||||
|
use gpui_native_core::button::Button;
|
||||||
|
use gpui_native_core::tooltip::Tooltip;
|
||||||
|
use gpui_native_theme::Theme;
|
||||||
|
|
||||||
|
/// Hover target at the top-left with a tooltip whose builder records every
|
||||||
|
/// invocation.
|
||||||
|
struct Harness {
|
||||||
|
calls: Rc<RefCell<Vec<&'static str>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Harness {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let calls = self.calls.clone();
|
||||||
|
let target = div()
|
||||||
|
.id("hover-target")
|
||||||
|
.w(px(100.))
|
||||||
|
.h(px(40.))
|
||||||
|
.bg(gpui::black())
|
||||||
|
.tooltip(move |_, cx| {
|
||||||
|
calls.borrow_mut().push("target");
|
||||||
|
Tooltip::new("Target tooltip", cx).into()
|
||||||
|
});
|
||||||
|
|
||||||
|
let button_calls = self.calls.clone();
|
||||||
|
let button = Button::new("hover-button")
|
||||||
|
.label("Hover me")
|
||||||
|
.tooltip(move |_, cx| {
|
||||||
|
button_calls.borrow_mut().push("button");
|
||||||
|
Tooltip::new("Button tooltip", cx).into()
|
||||||
|
});
|
||||||
|
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.child(target)
|
||||||
|
.child(div().mt_12().child(button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tooltip_window() -> (
|
||||||
|
TestAppContext,
|
||||||
|
VisualTestContext,
|
||||||
|
Rc<RefCell<Vec<&'static str>>>,
|
||||||
|
) {
|
||||||
|
let mut cx = TestAppContext::single();
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let calls = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
let harness_calls = calls.clone();
|
||||||
|
let window = cx.add_window(move |_, _| Harness {
|
||||||
|
calls: harness_calls,
|
||||||
|
});
|
||||||
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||||||
|
visual.update(|window, cx| {
|
||||||
|
window.draw(cx).clear(cx);
|
||||||
|
});
|
||||||
|
(cx, visual, calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover(visual: &mut VisualTestContext, position: gpui::Point<gpui::Pixels>) {
|
||||||
|
visual.simulate_event(MouseMoveEvent {
|
||||||
|
position,
|
||||||
|
pressed_button: None,
|
||||||
|
modifiers: Modifiers::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gpui's hover delay before deploying a tooltip.
|
||||||
|
const SHOW_DELAY: std::time::Duration = std::time::Duration::from_millis(500);
|
||||||
|
|
||||||
|
fn draw(visual: &mut VisualTestContext) {
|
||||||
|
visual.update(|window, cx| {
|
||||||
|
window.draw(cx).clear(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hovering_the_target_deploys_the_tooltip_after_the_delay() {
|
||||||
|
let (cx, mut visual, calls) = tooltip_window();
|
||||||
|
hover(&mut visual, point(px(50.), px(20.)));
|
||||||
|
assert!(
|
||||||
|
calls.borrow().is_empty(),
|
||||||
|
"builder runs only after the delay"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
calls.borrow().is_empty(),
|
||||||
|
"builder runs only after the delay"
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.executor().advance_clock(SHOW_DELAY);
|
||||||
|
visual.run_until_parked();
|
||||||
|
draw(&mut visual);
|
||||||
|
assert_eq!(*calls.borrow(), vec!["target"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hovering_elsewhere_deploys_nothing() {
|
||||||
|
let (cx, mut visual, calls) = tooltip_window();
|
||||||
|
hover(&mut visual, point(px(800.), px(600.)));
|
||||||
|
cx.executor().advance_clock(SHOW_DELAY);
|
||||||
|
visual.run_until_parked();
|
||||||
|
draw(&mut visual);
|
||||||
|
assert!(calls.borrow().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_button_passthrough_deploys_the_tooltip() {
|
||||||
|
let (cx, mut visual, calls) = tooltip_window();
|
||||||
|
hover(&mut visual, point(px(50.), px(100.)));
|
||||||
|
cx.executor().advance_clock(SHOW_DELAY);
|
||||||
|
visual.run_until_parked();
|
||||||
|
draw(&mut visual);
|
||||||
|
assert_eq!(*calls.borrow(), vec!["button"]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user