add tooltip

This commit is contained in:
2026-10-29 20:16:27 +07:00
parent 2f81f2848d
commit ae39ee35f7
5 changed files with 42 additions and 12 deletions
+1
View File
@@ -28,6 +28,7 @@ full = [
"input", "input",
"popover", "popover",
"scroll_area", "scroll_area",
"tooltip",
"menu", "menu",
"dialog", "dialog",
"sheet", "sheet",
+18 -1
View File
@@ -2,17 +2,20 @@
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
App, BoxShadow, ClickEvent, ElementId, InteractiveElement as _, IntoElement, AnyView, App, BoxShadow, ClickEvent, ElementId, InteractiveElement as _, IntoElement,
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _, ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
Styled as _, Window, px, relative, Styled as _, Window, px, relative,
}; };
use gpui_native_icons::{IconRole, icon}; use gpui_native_icons::{IconRole, icon};
use gpui_native_theme::{ActiveTheme as _, Size}; use gpui_native_theme::{ActiveTheme as _, Size};
use std::rc::Rc;
use crate::common::{Sizable, h_flex}; use crate::common::{Sizable, h_flex};
/// The single activation handler, shared by pointer, Enter, and Space. /// The single activation handler, shared by pointer, Enter, and Space.
type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>; type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
/// The tooltip view builder gpui's `.tooltip(...)` expects.
type TooltipBuilder = Rc<dyn Fn(&mut Window, &mut App) -> AnyView>;
/// Which visual treatment a button uses. /// Which visual treatment a button uses.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -38,6 +41,7 @@ pub struct Button {
size: Size, size: Size,
disabled: bool, disabled: bool,
a11y_label: Option<SharedString>, a11y_label: Option<SharedString>,
tooltip: Option<TooltipBuilder>,
on_click: Option<ClickHandler>, on_click: Option<ClickHandler>,
} }
@@ -55,6 +59,7 @@ impl Button {
size: Size::default(), size: Size::default(),
disabled: false, disabled: false,
a11y_label: None, a11y_label: None,
tooltip: None,
on_click: None, on_click: None,
} }
} }
@@ -74,6 +79,15 @@ impl Button {
self self
} }
/// Shows a tooltip view next to the button after it is hovered.
pub fn tooltip<F>(mut self, tooltip: F) -> Self
where
F: Fn(&mut Window, &mut App) -> AnyView + 'static,
{
self.tooltip = Some(Rc::new(tooltip));
self
}
/// Sets the visual treatment. /// Sets the visual treatment.
pub fn variant(mut self, variant: ButtonVariant) -> Self { pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = variant; self.variant = variant;
@@ -173,6 +187,9 @@ impl RenderOnce for Button {
}) })
.when_some(self.label, |button, label| button.child(label)) .when_some(self.label, |button, label| button.child(label))
.when(self.disabled, |button| button.opacity(0.5)) .when(self.disabled, |button| button.opacity(0.5))
.when_some(self.tooltip, |button, tooltip| {
button.tooltip(move |window, cx| tooltip(window, cx))
})
.when(!self.disabled, |button| { .when(!self.disabled, |button| {
button button
.cursor_pointer() .cursor_pointer()
+3
View File
@@ -45,6 +45,9 @@ pub mod popover;
#[cfg(feature = "scroll_area")] #[cfg(feature = "scroll_area")]
pub mod scroll_area; pub mod scroll_area;
#[cfg(feature = "tooltip")]
pub mod tooltip;
/// Initializes everything the components need at startup. /// Initializes everything the components need at startup.
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
gpui_native_theme::Theme::init(cx); gpui_native_theme::Theme::init(cx);
+1 -1
View File
@@ -212,7 +212,7 @@ Each row is one review unit: build it, show it in the gallery, review, then move
| 11.5 | core | ScrollArea + Scrollbar | ✅ Landed (2026-10-29). Inserted before the remaining overlays — Menus, Lists and Tables all scroll. gpui owns the mechanics (`overflow_*_scroll` + caller-held `ScrollHandle`); the component paints an overlay thumb (`scrollbar_thumb` token, `Scrollbar` metrics) with drag and geometry unit-tested. Wheel input is gpui's; the area stops wheel propagation so nested areas don't co-scroll. Horizontal thumb deferred; auto-hide on idle deferred | | 11.5 | core | ScrollArea + Scrollbar | ✅ Landed (2026-10-29). Inserted before the remaining overlays — Menus, Lists and Tables all scroll. gpui owns the mechanics (`overflow_*_scroll` + caller-held `ScrollHandle`); the component paints an overlay thumb (`scrollbar_thumb` token, `Scrollbar` metrics) with drag and geometry unit-tested. Wheel input is gpui's; the area stops wheel propagation so nested areas don't co-scroll. Horizontal thumb deferred; auto-hide on idle deferred |
| 12 | core | Menu | ✅ Landed (2026-10-29). Controlled `open`/`on_change` + `trigger`/`items` (MenuItem: label, icon, shortcut hint, disabled, destructive, checked, separator). Popover shell patterns duplicated per D16 (promote at Dialog); panel = surface + overlay radius + popover elevation, role Menu; claims focus while open; keyboard nav — arrows/home/end over selectable items (wrapping, skipping separators + disabled), enter/space activates (closes menu before the action), escape dismisses; hover selects, click activates; trigger clicks toggle (popover-owned); selection resets on each open; long menus scroll via the ScrollArea once a height is styled. Deferred: submenus, type-ahead, scroll-selected-item-into-view, real shortcut handling | | 12 | core | Menu | ✅ Landed (2026-10-29). Controlled `open`/`on_change` + `trigger`/`items` (MenuItem: label, icon, shortcut hint, disabled, destructive, checked, separator). Popover shell patterns duplicated per D16 (promote at Dialog); panel = surface + overlay radius + popover elevation, role Menu; claims focus while open; keyboard nav — arrows/home/end over selectable items (wrapping, skipping separators + disabled), enter/space activates (closes menu before the action), escape dismisses; hover selects, click activates; trigger clicks toggle (popover-owned); selection resets on each open; long menus scroll via the ScrollArea once a height is styled. Deferred: submenus, type-ahead, scroll-selected-item-into-view, real shortcut handling |
| 11 | core | Popover | ✅ Landed (2026-10-29). First overlay. Controlled `open` + `on_change` (Rc-shared handler); `trigger`/`content` element closures; panel = surface + `Radius.overlay` + `Elevations.popover` + border, role Dialog with label; `anchored()` + `deferred()` (window-overflow flip for free); enter fade via gpui `with_animation` at `Motion.overlay` (respects reduce-motion); claims keyboard focus while open; escape and click-outside dismiss (outside clicks swallowed). No scrim — native macOS popover model; the overlay token waits for Dialog. Known v1 gaps: no exit animation, trigger aria-expanded left to the app | | 11 | core | Popover | ✅ Landed (2026-10-29). First overlay. Controlled `open` + `on_change` (Rc-shared handler); `trigger`/`content` element closures; panel = surface + `Radius.overlay` + `Elevations.popover` + border, role Dialog with label; `anchored()` + `deferred()` (window-overflow flip for free); enter fade via gpui `with_animation` at `Motion.overlay` (respects reduce-motion); claims keyboard focus while open; escape and click-outside dismiss (outside clicks swallowed). No scrim — native macOS popover model; the overlay token waits for Dialog. Known v1 gaps: no exit animation, trigger aria-expanded left to the app |
| 11+ | core | Tooltip, Menu, Dialog, AlertDialog, Sheet, Toast | One unit each, same gate. Menu reuses the popover shell + `Radius.overlay`/`Elevations.popover`; Dialog consumes `colors.overlay` (scrim) + `Elevations.dialog` | | 11+ | core | Menu ✅ (2026-10-29), Tooltip ✅ (2026-10-29), Dialog, AlertDialog, Sheet, Toast | One unit each, same gate. Menu reuses the popover shell + `Radius.overlay`/`Elevations.popover`; Dialog consumes `colors.overlay` (scrim) + `Elevations.dialog`. Tooltip rides gpui's own `.tooltip()` lifecycle (hover delay, anchoring, dismissal — all framework-owned); ours is the styled view (`surface` + border + `Radius.sm`, small text) plus a `Tooltip::text` closure and a Button passthrough |
| then | data (new) | List, Table, Tree, Tabs, Sidebar | Created on demand, same gate. | | then | data (new) | List, Table, Tree, Tabs, Sidebar | Created on demand, same gate. |
| then | pickers or data | Calendar/DatePicker, TimeField, ColorPicker | Decide at unit time. | | then | pickers or data | Calendar/DatePicker, TimeField, ColorPicker | Decide at unit time. |
| editor | editor (new) | Text/code editor, syntax highlighting (D12) | Same gate. | | editor | editor (new) | Text/code editor, syntax highlighting (D12) | Same gate. |
+19 -10
View File
@@ -18,6 +18,7 @@ use gpui_native_core::scroll_area::ScrollArea;
use gpui_native_core::slider::Slider; use gpui_native_core::slider::Slider;
use gpui_native_core::switch::Switch; use gpui_native_core::switch::Switch;
use gpui_native_core::toggle::Toggle; use gpui_native_core::toggle::Toggle;
use gpui_native_core::tooltip::Tooltip;
use gpui_native_icons::{IconAssets, IconRole, icon}; use gpui_native_icons::{IconAssets, IconRole, icon};
use gpui_native_theme::{ActiveTheme as _, Theme}; use gpui_native_theme::{ActiveTheme as _, Theme};
use gpui_platform::application; use gpui_platform::application;
@@ -190,15 +191,22 @@ impl Render for Gallery {
.gap_2() .gap_2()
.children(button_variants.map(|(name, variant)| { .children(button_variants.map(|(name, variant)| {
let gallery = gallery.clone(); let gallery = gallery.clone();
Button::new(SharedString::from(format!("gallery-button-{name}"))) let mut button = Button::new(SharedString::from(format!(
.label(button_label(variant)) "gallery-button-{name}"
.variant(variant) )))
.on_click(move |_, _, cx| { .label(button_label(variant))
gallery.update(cx, |gallery, cx| { .variant(variant)
gallery.button_clicks += 1; .on_click(move |_, _, cx| {
cx.notify(); gallery.update(cx, |gallery, cx| {
}); gallery.button_clicks += 1;
}) cx.notify();
});
});
if variant == ButtonVariant::Destructive {
button =
button.tooltip(Tooltip::text("Removes the selected item"));
}
button
})) }))
.child( .child(
Button::new("gallery-button-disabled") Button::new("gallery-button-disabled")
@@ -208,7 +216,8 @@ impl Render for Gallery {
.child( .child(
Button::new("gallery-button-icon") Button::new("gallery-button-icon")
.label("Open") .label("Open")
.icon(IconRole::Folder), .icon(IconRole::Folder)
.tooltip(Tooltip::text("Opens the selected file")),
), ),
) )
.child( .child(