audit tooltip
This commit is contained in:
@@ -3,12 +3,12 @@
|
|||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, ParentElement as _,
|
AnyView, App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, ParentElement as _,
|
||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Window, hsla,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Window, canvas,
|
||||||
relative,
|
hsla, relative,
|
||||||
};
|
};
|
||||||
use gpui_native_icons::{IconRole, icon};
|
use gpui_native_icons::{IconRole, icon};
|
||||||
use gpui_native_theme::{ActiveTheme as _, BORDER_WIDTH, Size};
|
use gpui_native_theme::{ActiveTheme as _, BORDER_WIDTH, Size};
|
||||||
use gpui_native_utils::focus_ring;
|
use gpui_native_utils::{focus_ring, record_anchor_bounds};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::common::{Sizable, h_flex};
|
use crate::common::{Sizable, h_flex};
|
||||||
@@ -241,6 +241,11 @@ impl RenderOnce for Button {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when_some(self.label, |button, label| button.child(label))
|
.when_some(self.label, |button, label| button.child(label))
|
||||||
|
.child(
|
||||||
|
canvas(record_anchor_bounds(self.id.clone()), |_, _, _, _| {})
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
)
|
||||||
.when(self.disabled, |button| button.opacity(0.5))
|
.when(self.disabled, |button| button.opacity(0.5))
|
||||||
.when_some(self.tooltip, |button, tooltip| {
|
.when_some(self.tooltip, |button, tooltip| {
|
||||||
button.tooltip(move |window, cx| tooltip(window, cx))
|
button.tooltip(move |window, cx| tooltip(window, cx))
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
use gpui::AppContext as _;
|
use gpui::AppContext as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, App, Context, Entity, IntoElement, ParentElement as _, Render, SharedString,
|
AnyView, App, Bounds, Context, ElementId, Entity, InteractiveElement as _, IntoElement,
|
||||||
Styled as _, Window, div, px,
|
ParentElement as _, Pixels, Render, SharedString, Styled as _, Window, canvas, div, point, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, BORDER_WIDTH};
|
||||||
|
use gpui_native_utils::{anchor_bounds, record_anchor_bounds};
|
||||||
|
|
||||||
|
const PANEL_BOUNDS_ID: &str = "tooltip-panel-bounds";
|
||||||
|
|
||||||
/// A small text panel gpui shows next to a hovered element.
|
/// A small text panel gpui shows next to a hovered element.
|
||||||
pub struct Tooltip {
|
pub struct Tooltip {
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
|
anchor: Option<Bounds<Pixels>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tooltip {
|
impl Tooltip {
|
||||||
@@ -16,7 +20,28 @@ impl Tooltip {
|
|||||||
where
|
where
|
||||||
L: Into<SharedString>,
|
L: Into<SharedString>,
|
||||||
{
|
{
|
||||||
cx.new(|_| Self { text: text.into() })
|
cx.new(|_| Self {
|
||||||
|
text: text.into(),
|
||||||
|
anchor: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the anchor element ID for the tooltip.
|
||||||
|
pub fn anchored<L, I>(text: L, anchor_id: I) -> impl Fn(&mut Window, &mut App) -> AnyView
|
||||||
|
where
|
||||||
|
L: Into<SharedString>,
|
||||||
|
I: Into<ElementId>,
|
||||||
|
{
|
||||||
|
let text = text.into();
|
||||||
|
let anchor_id = anchor_id.into();
|
||||||
|
move |_window, cx| {
|
||||||
|
let anchor = anchor_bounds(&anchor_id, cx);
|
||||||
|
cx.new(|_| Self {
|
||||||
|
text: text.clone(),
|
||||||
|
anchor,
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `.tooltip(...)` closure for a plain text tooltip.
|
/// The `.tooltip(...)` closure for a plain text tooltip.
|
||||||
@@ -30,20 +55,79 @@ impl Tooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Tooltip {
|
impl Render for Tooltip {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
// Copied out: the mutable reads below need `cx` free of this borrow.
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let text_size = theme.sizes.small.text_size;
|
let text_size = theme.sizes.small.text_size;
|
||||||
|
let spacing_unit = theme.spacing.unit;
|
||||||
|
let surface = theme.colors.surface;
|
||||||
|
let surface_foreground = theme.colors.surface_foreground;
|
||||||
|
let radius_sm = theme.radius.sm;
|
||||||
|
let shadows = theme.elevations.popover.shadows();
|
||||||
|
|
||||||
|
let border = if theme.borders.overlays {
|
||||||
|
theme.colors.border
|
||||||
|
} else {
|
||||||
|
theme.colors.surface
|
||||||
|
};
|
||||||
|
|
||||||
|
let build_panel = || {
|
||||||
div()
|
div()
|
||||||
|
.debug_selector(|| "tooltip-panel".to_string())
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_1()
|
.py_1()
|
||||||
.max_w(px(360.))
|
.max_w(px(360.))
|
||||||
.bg(theme.colors.surface)
|
.bg(surface)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(theme.colors.border)
|
.border_color(border)
|
||||||
.rounded(theme.radius.sm)
|
.rounded(radius_sm)
|
||||||
|
.shadow(shadows.clone())
|
||||||
.text_size(text_size)
|
.text_size(text_size)
|
||||||
.text_color(theme.colors.surface_foreground)
|
.text_color(surface_foreground)
|
||||||
.child(self.text.clone())
|
.child(self.text.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (wrapper, panel) = match self.anchor {
|
||||||
|
Some(parent) => {
|
||||||
|
let origin = window.mouse_position() + point(px(1.), px(1.));
|
||||||
|
let viewport_height = window.viewport_size().height;
|
||||||
|
|
||||||
|
let height = anchor_bounds(&ElementId::from(PANEL_BOUNDS_ID), cx)
|
||||||
|
.map(|bounds| bounds.size.height)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
text_size * 1.5 + window.rem_size() * 0.5 + BORDER_WIDTH * 2.0
|
||||||
|
});
|
||||||
|
|
||||||
|
let below = viewport_height - spacing_unit - parent.bottom();
|
||||||
|
let above = parent.top() - spacing_unit;
|
||||||
|
let hang_below = below >= height || below >= above;
|
||||||
|
|
||||||
|
let panel = build_panel()
|
||||||
|
.ml(parent.left() - origin.x)
|
||||||
|
.mt(if hang_below {
|
||||||
|
parent.bottom() + spacing_unit - origin.y
|
||||||
|
} else {
|
||||||
|
parent.top() - spacing_unit - height - origin.y
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
canvas(
|
||||||
|
record_anchor_bounds(ElementId::from(PANEL_BOUNDS_ID)),
|
||||||
|
|_, _, _, _| {},
|
||||||
|
)
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
);
|
||||||
|
(div().debug_selector(|| "tooltip-anchor".to_string()), panel)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let wrapper = div()
|
||||||
|
.debug_selector(|| "tooltip-anchor".to_string())
|
||||||
|
.pl(spacing_unit * 2.0)
|
||||||
|
.pt(spacing_unit * 2.0);
|
||||||
|
(wrapper, build_panel())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapper.child(panel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
use std::cell::RefCell;
|
use std::{cell::RefCell, rc::Rc};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Context, InteractiveElement as _, IntoElement, Modifiers, MouseMoveEvent, ParentElement as _,
|
Context, InteractiveElement as _, IntoElement, Modifiers, MouseMoveEvent, ParentElement as _,
|
||||||
Render, StatefulInteractiveElement as _, Styled as _, TestAppContext, VisualTestContext,
|
Render, StatefulInteractiveElement as _, Styled as _, TestAppContext, VisualTestContext,
|
||||||
Window, div, point, px,
|
Window, WindowHandle, canvas, div, point, px,
|
||||||
};
|
};
|
||||||
use gpui_native_core::button::Button;
|
use gpui_native_core::button::Button;
|
||||||
use gpui_native_core::tooltip::Tooltip;
|
use gpui_native_core::tooltip::Tooltip;
|
||||||
use gpui_native_theme::Theme;
|
use gpui_native_theme::Theme;
|
||||||
|
use gpui_native_utils::record_anchor_bounds;
|
||||||
|
|
||||||
/// Hover target at the top-left with a tooltip whose builder records every
|
/// Hover target at the top-left with a tooltip whose builder records every
|
||||||
/// invocation.
|
/// invocation. The tooltip anchors to the target itself, so it hangs below
|
||||||
|
/// the target's bottom edge instead of at the cursor.
|
||||||
struct Harness {
|
struct Harness {
|
||||||
calls: Rc<RefCell<Vec<&'static str>>>,
|
calls: Rc<RefCell<Vec<&'static str>>>,
|
||||||
}
|
}
|
||||||
@@ -21,20 +22,26 @@ impl Render for Harness {
|
|||||||
let calls = self.calls.clone();
|
let calls = self.calls.clone();
|
||||||
let target = div()
|
let target = div()
|
||||||
.id("hover-target")
|
.id("hover-target")
|
||||||
|
.debug_selector(|| "hover-target".to_string())
|
||||||
.w(px(100.))
|
.w(px(100.))
|
||||||
.h(px(40.))
|
.h(px(40.))
|
||||||
.bg(gpui::black())
|
.bg(gpui::black())
|
||||||
.tooltip(move |_, cx| {
|
.child(
|
||||||
|
canvas(record_anchor_bounds("hover-target".into()), |_, _, _, _| {})
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
)
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
calls.borrow_mut().push("target");
|
calls.borrow_mut().push("target");
|
||||||
Tooltip::new("Target tooltip", cx).into()
|
Tooltip::anchored("Target tooltip", "hover-target")(window, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
let button_calls = self.calls.clone();
|
let button_calls = self.calls.clone();
|
||||||
let button = Button::new("hover-button")
|
let button = Button::new("hover-button")
|
||||||
.label("Hover me")
|
.label("Hover me")
|
||||||
.tooltip(move |_, cx| {
|
.tooltip(move |window, cx| {
|
||||||
button_calls.borrow_mut().push("button");
|
button_calls.borrow_mut().push("button");
|
||||||
Tooltip::new("Button tooltip", cx).into()
|
Tooltip::anchored("Button tooltip", "hover-button")(window, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
div()
|
div()
|
||||||
@@ -80,6 +87,40 @@ fn draw(visual: &mut VisualTestContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LowHarness;
|
||||||
|
|
||||||
|
impl Render for LowHarness {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
// The target is pinned to the window's bottom edge: the space below it
|
||||||
|
// is nil, so the tooltip must flip above the parent.
|
||||||
|
div().size_full().flex().flex_col().justify_end().child(
|
||||||
|
div()
|
||||||
|
.id("low-target")
|
||||||
|
.debug_selector(|| "low-target".to_string())
|
||||||
|
.w(px(100.))
|
||||||
|
.h(px(40.))
|
||||||
|
.bg(gpui::black())
|
||||||
|
.child(
|
||||||
|
canvas(record_anchor_bounds("low-target".into()), |_, _, _, _| {})
|
||||||
|
.absolute()
|
||||||
|
.inset_0(),
|
||||||
|
)
|
||||||
|
.tooltip(Tooltip::anchored("Low tooltip", "low-target")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_tooltip_window() -> (TestAppContext, VisualTestContext, WindowHandle<LowHarness>) {
|
||||||
|
let mut cx = TestAppContext::single();
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let window = cx.add_window(move |_, _| LowHarness);
|
||||||
|
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||||||
|
visual.update(|window, cx| {
|
||||||
|
window.draw(cx).clear(cx);
|
||||||
|
});
|
||||||
|
(cx, visual, window)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hovering_the_target_deploys_the_tooltip_after_the_delay() {
|
fn hovering_the_target_deploys_the_tooltip_after_the_delay() {
|
||||||
let (cx, mut visual, calls) = tooltip_window();
|
let (cx, mut visual, calls) = tooltip_window();
|
||||||
@@ -118,3 +159,65 @@ fn the_button_passthrough_deploys_the_tooltip() {
|
|||||||
draw(&mut visual);
|
draw(&mut visual);
|
||||||
assert_eq!(*calls.borrow(), vec!["button"]);
|
assert_eq!(*calls.borrow(), vec!["button"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An anchored tooltip hangs below the hovered parent's bottom edge with the
|
||||||
|
/// spacing gap: it never covers the parent, wherever the cursor is inside it.
|
||||||
|
#[test]
|
||||||
|
fn the_anchored_panel_hangs_below_the_parent_without_overlapping() {
|
||||||
|
let (cx, mut visual, _calls) = tooltip_window();
|
||||||
|
hover(&mut visual, point(px(50.), px(20.)));
|
||||||
|
cx.executor().advance_clock(SHOW_DELAY);
|
||||||
|
visual.run_until_parked();
|
||||||
|
draw(&mut visual);
|
||||||
|
|
||||||
|
let target = visual
|
||||||
|
.debug_bounds("hover-target")
|
||||||
|
.expect("target laid out");
|
||||||
|
let panel = visual
|
||||||
|
.debug_bounds("tooltip-panel")
|
||||||
|
.expect("panel rendered");
|
||||||
|
|
||||||
|
let unit = cx.update(|cx| Theme::global(cx).spacing.unit);
|
||||||
|
assert!(
|
||||||
|
(panel.top() - target.bottom() - unit).abs() <= px(0.5),
|
||||||
|
"the panel hangs {gap:?} below the target",
|
||||||
|
gap = panel.top() - target.bottom()
|
||||||
|
);
|
||||||
|
assert!((panel.left() - target.left()).abs() <= px(0.5));
|
||||||
|
assert!(
|
||||||
|
panel.top() >= target.bottom(),
|
||||||
|
"the panel must never overlap the hovered parent"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A parent pinned to the window's bottom edge leaves no room below: the
|
||||||
|
/// panel flips above the parent's top edge instead.
|
||||||
|
#[test]
|
||||||
|
fn an_anchored_tooltip_flips_above_a_low_parent() {
|
||||||
|
let (mut cx, mut visual, window) = low_tooltip_window();
|
||||||
|
|
||||||
|
let window_height = window
|
||||||
|
.update(&mut cx, |_, window, _cx| window.viewport_size().height)
|
||||||
|
.expect("window alive");
|
||||||
|
hover(&mut visual, point(px(50.), window_height - px(20.)));
|
||||||
|
cx.executor().advance_clock(SHOW_DELAY);
|
||||||
|
visual.run_until_parked();
|
||||||
|
draw(&mut visual);
|
||||||
|
|
||||||
|
let target = visual.debug_bounds("low-target").expect("target laid out");
|
||||||
|
let panel = visual
|
||||||
|
.debug_bounds("tooltip-panel")
|
||||||
|
.expect("panel rendered");
|
||||||
|
|
||||||
|
// The flip hangs the panel above the parent's top edge.
|
||||||
|
assert!(
|
||||||
|
panel.bottom() <= target.top(),
|
||||||
|
"the panel must not cover the parent: panel bottom {:?}, parent top {:?}",
|
||||||
|
panel.bottom(),
|
||||||
|
target.top()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
panel.top() >= px(0.),
|
||||||
|
"the flipped panel must stay inside the window"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+39
-2
@@ -1,9 +1,10 @@
|
|||||||
use gpui::prelude::FluentBuilder as _;
|
use gpui::prelude::FluentBuilder as _;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, BoxShadow, Div, ElementId, FocusHandle, Hsla, IntoElement, Refineable as _,
|
App, Bounds, BoxShadow, Div, ElementId, FocusHandle, Global, Hsla, IntoElement, Pixels,
|
||||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, div, px,
|
Refineable as _, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::ActiveTheme as _;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// A row whose children center on the cross axis.
|
/// A row whose children center on the cross axis.
|
||||||
pub fn h_flex() -> Div {
|
pub fn h_flex() -> Div {
|
||||||
@@ -35,6 +36,42 @@ pub fn focus_handle(id: &ElementId, window: &mut Window, cx: &mut App) -> FocusH
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The painted bounds of elements that tooltips anchor to, keyed by element id.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct AnchorBoundsRegistry(HashMap<ElementId, Bounds<Pixels>>);
|
||||||
|
|
||||||
|
impl Global for AnchorBoundsRegistry {}
|
||||||
|
|
||||||
|
impl AnchorBoundsRegistry {
|
||||||
|
fn record(&mut self, id: ElementId, bounds: Bounds<Pixels>) {
|
||||||
|
self.0.insert(id, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, id: &ElementId) -> Option<Bounds<Pixels>> {
|
||||||
|
self.0.get(id).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The last bounds an element painted at.
|
||||||
|
pub fn anchor_bounds(id: &ElementId, cx: &mut App) -> Option<Bounds<Pixels>> {
|
||||||
|
cx.try_global::<AnchorBoundsRegistry>()
|
||||||
|
.and_then(|registry| registry.get(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A canvas prepaint closure recording the element's painted bounds.
|
||||||
|
pub fn record_anchor_bounds(
|
||||||
|
id: ElementId,
|
||||||
|
) -> impl Fn(Bounds<Pixels>, &mut Window, &mut App) + 'static {
|
||||||
|
move |bounds, _window, cx| {
|
||||||
|
let mut registry = cx
|
||||||
|
.try_global::<AnchorBoundsRegistry>()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
registry.record(id.clone(), bounds);
|
||||||
|
cx.set_global(registry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a focus ring shadow with the given color at half ink.
|
/// Returns a focus ring shadow with the given color at half ink.
|
||||||
pub fn focus_ring(color: Hsla) -> Vec<BoxShadow> {
|
pub fn focus_ring(color: Hsla) -> Vec<BoxShadow> {
|
||||||
vec![BoxShadow::new(px(0.), px(0.), color.alpha(0.5)).spread_radius(px(3.))]
|
vec![BoxShadow::new(px(0.), px(0.), color.alpha(0.5)).spread_radius(px(3.))]
|
||||||
|
|||||||
+10
-1
@@ -14,7 +14,6 @@ is the first downstream consumer; its screens drive the order.
|
|||||||
Platform metric adoption continues one component at a time; each stop goes
|
Platform metric adoption continues one component at a time; each stop goes
|
||||||
through the review gate and a gallery pass.
|
through the review gate and a gallery pass.
|
||||||
|
|
||||||
- [ ] **Tooltip platform chrome** — overlay border gate.
|
|
||||||
- [ ] **Alert platform chrome** — overlay border gate.
|
- [ ] **Alert platform chrome** — overlay border gate.
|
||||||
- [ ] **Combobox platform chrome** — popup border behind
|
- [ ] **Combobox platform chrome** — popup border behind
|
||||||
`Theme::borders.overlays`; the trigger follows the input stop's field
|
`Theme::borders.overlays`; the trigger follows the input stop's field
|
||||||
@@ -118,6 +117,16 @@ Gaps surfaced by the signed comparison; ordered by signed's usage.
|
|||||||
`Theme::borders.overlays` (and dark mode), taking the surface color
|
`Theme::borders.overlays` (and dark mode), taking the surface color
|
||||||
where the platform paints none; the pill rides the popover elevation,
|
where the platform paints none; the pill rides the popover elevation,
|
||||||
so dark mode's no-shadow rule applies to it too
|
so dark mode's no-shadow rule applies to it too
|
||||||
|
- [x] Tooltip platform chrome — the panel border behind
|
||||||
|
`Theme::borders.overlays` (and dark mode), taking the surface color
|
||||||
|
where the platform paints none, and riding the popover elevation for
|
||||||
|
the light-mode shadow; anchored tooltips hang below the
|
||||||
|
hovered parent (`Tooltip::anchored(text, id)`) so they never cover it —
|
||||||
|
the element records its painted bounds with `record_anchor_bounds`
|
||||||
|
(`Button` does), the panel hangs below that with the spacing gap, and
|
||||||
|
it flips above the parent when it would overflow the window bottom
|
||||||
|
(the panel records its own height after the first frame); unanchored
|
||||||
|
tooltips fall back to the cursor-offset wrapper
|
||||||
- [x] Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider,
|
- [x] Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider,
|
||||||
Progress, Avatar (image + seeded pixel-art fallback)
|
Progress, Avatar (image + seeded pixel-art fallback)
|
||||||
- [x] Spinner — painted ring + rotating-icon variant, indeterminate and
|
- [x] Spinner — painted ring + rotating-icon variant, indeterminate and
|
||||||
|
|||||||
@@ -876,7 +876,9 @@ impl Render for ButtonDemo {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
if variant == ButtonVariant::Destructive {
|
if variant == ButtonVariant::Destructive {
|
||||||
button = button.tooltip(Tooltip::text("Removes the selected item"));
|
let id = SharedString::from(format!("button-demo-{name}"));
|
||||||
|
button = button
|
||||||
|
.tooltip(Tooltip::anchored("Removes the selected item", id));
|
||||||
}
|
}
|
||||||
button
|
button
|
||||||
})),
|
})),
|
||||||
@@ -901,7 +903,9 @@ impl Render for ButtonDemo {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
if variant == ButtonVariant::Destructive {
|
if variant == ButtonVariant::Destructive {
|
||||||
button = button.tooltip(Tooltip::text("Removes the selected item"));
|
let id = SharedString::from(format!("button-demo-icon-{name}"));
|
||||||
|
button = button
|
||||||
|
.tooltip(Tooltip::anchored("Removes the selected item", id));
|
||||||
}
|
}
|
||||||
button
|
button
|
||||||
})),
|
})),
|
||||||
@@ -914,11 +918,12 @@ impl Render for ButtonDemo {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.children(variants.map(|(name, variant)| {
|
.children(variants.map(|(name, variant)| {
|
||||||
let view = view.clone();
|
let view = view.clone();
|
||||||
Button::new(SharedString::from(format!("button-demo-icon-only-{name}")))
|
let id = SharedString::from(format!("button-demo-icon-only-{name}"));
|
||||||
|
Button::new(id.clone())
|
||||||
.icon(IconRole::Folder)
|
.icon(IconRole::Folder)
|
||||||
.variant(variant)
|
.variant(variant)
|
||||||
.accessibility_label("Open the selected file")
|
.accessibility_label("Open the selected file")
|
||||||
.tooltip(Tooltip::text("Opens the selected file"))
|
.tooltip(Tooltip::anchored("Opens the selected file", id))
|
||||||
.on_click(move |_, _, cx| {
|
.on_click(move |_, _, cx| {
|
||||||
view.update(cx, |demo, cx| {
|
view.update(cx, |demo, cx| {
|
||||||
demo.clicks += 1;
|
demo.clicks += 1;
|
||||||
@@ -2800,20 +2805,29 @@ impl Render for TooltipDemo {
|
|||||||
Button::new("tooltip-demo-destructive")
|
Button::new("tooltip-demo-destructive")
|
||||||
.label("Delete")
|
.label("Delete")
|
||||||
.variant(ButtonVariant::Destructive)
|
.variant(ButtonVariant::Destructive)
|
||||||
.tooltip(Tooltip::text("Removes the selected item")),
|
.tooltip(Tooltip::anchored(
|
||||||
|
"Removes the selected item",
|
||||||
|
"tooltip-demo-destructive",
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("tooltip-demo-icon")
|
Button::new("tooltip-demo-icon")
|
||||||
.label("Open")
|
.label("Open")
|
||||||
.icon(IconRole::Folder)
|
.icon(IconRole::Folder)
|
||||||
.tooltip(Tooltip::text("Opens the selected file")),
|
.tooltip(Tooltip::anchored(
|
||||||
|
"Opens the selected file",
|
||||||
|
"tooltip-demo-icon",
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("tooltip-demo-icon-only")
|
Button::new("tooltip-demo-icon-only")
|
||||||
.icon(IconRole::Folder)
|
.icon(IconRole::Folder)
|
||||||
.variant(ButtonVariant::Ghost)
|
.variant(ButtonVariant::Ghost)
|
||||||
.accessibility_label("Open the selected file")
|
.accessibility_label("Open the selected file")
|
||||||
.tooltip(Tooltip::text("Opens the selected file")),
|
.tooltip(Tooltip::anchored(
|
||||||
|
"Opens the selected file",
|
||||||
|
"tooltip-demo-icon-only",
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user