Allow hovering over tooltips in git blame
sidebar (#10466)
This introduces a new API on `StatefulInteractiveElement` to create a tooltip that can be hovered, scrolled inside, and clicked: `.hoverable_tooltip`. Right now we only use it in the `git blame` gutter, but the plan is to use the new hover/click/scroll behavior in #10398 to introduce new git-blame-tooltips. Release Notes: - N/A --------- Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
parent
bc0c2e0cae
commit
6e1ba7e936
7 changed files with 190 additions and 52 deletions
|
@ -2969,7 +2969,7 @@ fn render_blame_entry(
|
||||||
cx.open_url(url.as_str())
|
cx.open_url(url.as_str())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.tooltip(move |cx| {
|
.hoverable_tooltip(move |cx| {
|
||||||
BlameEntryTooltip::new(
|
BlameEntryTooltip::new(
|
||||||
sha_color.cursor,
|
sha_color.cursor,
|
||||||
commit_message.clone(),
|
commit_message.clone(),
|
||||||
|
|
|
@ -1416,8 +1416,8 @@ pub struct AnyTooltip {
|
||||||
/// The view used to display the tooltip
|
/// The view used to display the tooltip
|
||||||
pub view: AnyView,
|
pub view: AnyView,
|
||||||
|
|
||||||
/// The offset from the cursor to use, relative to the parent view
|
/// The absolute position of the mouse when the tooltip was deployed.
|
||||||
pub cursor_offset: Point<Pixels>,
|
pub mouse_position: Point<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A keystroke event, and potentially the associated action
|
/// A keystroke event, and potentially the associated action
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
||||||
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
|
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
||||||
StyleRefinement, Styled, Task, View, Visibility, WindowContext,
|
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
@ -483,7 +483,29 @@ impl Interactivity {
|
||||||
self.tooltip_builder.is_none(),
|
self.tooltip_builder.is_none(),
|
||||||
"calling tooltip more than once on the same element is not supported"
|
"calling tooltip more than once on the same element is not supported"
|
||||||
);
|
);
|
||||||
self.tooltip_builder = Some(Rc::new(build_tooltip));
|
self.tooltip_builder = Some(TooltipBuilder {
|
||||||
|
build: Rc::new(build_tooltip),
|
||||||
|
hoverable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
|
||||||
|
/// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
|
||||||
|
/// the tooltip. The imperative API equivalent to [`InteractiveElement::hoverable_tooltip`]
|
||||||
|
pub fn hoverable_tooltip(
|
||||||
|
&mut self,
|
||||||
|
build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static,
|
||||||
|
) where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
debug_assert!(
|
||||||
|
self.tooltip_builder.is_none(),
|
||||||
|
"calling tooltip more than once on the same element is not supported"
|
||||||
|
);
|
||||||
|
self.tooltip_builder = Some(TooltipBuilder {
|
||||||
|
build: Rc::new(build_tooltip),
|
||||||
|
hoverable: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block the mouse from interacting with this element or any of its children
|
/// Block the mouse from interacting with this element or any of its children
|
||||||
|
@ -973,6 +995,20 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
||||||
self.interactivity().tooltip(build_tooltip);
|
self.interactivity().tooltip(build_tooltip);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
|
||||||
|
/// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into
|
||||||
|
/// the tooltip. The fluent API equivalent to [`Interactivity::hoverable_tooltip`]
|
||||||
|
fn hoverable_tooltip(
|
||||||
|
mut self,
|
||||||
|
build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.interactivity().hoverable_tooltip(build_tooltip);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for providing focus related APIs to interactive elements
|
/// A trait for providing focus related APIs to interactive elements
|
||||||
|
@ -1015,7 +1051,10 @@ type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
|
||||||
|
|
||||||
type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>;
|
type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>;
|
||||||
|
|
||||||
pub(crate) type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
|
pub(crate) struct TooltipBuilder {
|
||||||
|
build: Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
|
||||||
|
hoverable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) type KeyDownListener =
|
pub(crate) type KeyDownListener =
|
||||||
Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
|
Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
|
||||||
|
@ -1188,6 +1227,7 @@ pub struct Interactivity {
|
||||||
/// Whether the element was hovered. This will only be present after paint if an hitbox
|
/// Whether the element was hovered. This will only be present after paint if an hitbox
|
||||||
/// was created for the interactive element.
|
/// was created for the interactive element.
|
||||||
pub hovered: Option<bool>,
|
pub hovered: Option<bool>,
|
||||||
|
pub(crate) tooltip_id: Option<TooltipId>,
|
||||||
pub(crate) content_size: Size<Pixels>,
|
pub(crate) content_size: Size<Pixels>,
|
||||||
pub(crate) key_context: Option<KeyContext>,
|
pub(crate) key_context: Option<KeyContext>,
|
||||||
pub(crate) focusable: bool,
|
pub(crate) focusable: bool,
|
||||||
|
@ -1321,7 +1361,7 @@ impl Interactivity {
|
||||||
if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
|
if let Some(active_tooltip) = element_state.active_tooltip.as_ref() {
|
||||||
if let Some(active_tooltip) = active_tooltip.borrow().as_ref() {
|
if let Some(active_tooltip) = active_tooltip.borrow().as_ref() {
|
||||||
if let Some(tooltip) = active_tooltip.tooltip.clone() {
|
if let Some(tooltip) = active_tooltip.tooltip.clone() {
|
||||||
cx.set_tooltip(tooltip);
|
self.tooltip_id = Some(cx.set_tooltip(tooltip));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1806,6 +1846,7 @@ impl Interactivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tooltip_builder) = self.tooltip_builder.take() {
|
if let Some(tooltip_builder) = self.tooltip_builder.take() {
|
||||||
|
let tooltip_is_hoverable = tooltip_builder.hoverable;
|
||||||
let active_tooltip = element_state
|
let active_tooltip = element_state
|
||||||
.active_tooltip
|
.active_tooltip
|
||||||
.get_or_insert_with(Default::default)
|
.get_or_insert_with(Default::default)
|
||||||
|
@ -1818,11 +1859,17 @@ impl Interactivity {
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let active_tooltip = active_tooltip.clone();
|
let active_tooltip = active_tooltip.clone();
|
||||||
let hitbox = hitbox.clone();
|
let hitbox = hitbox.clone();
|
||||||
|
let tooltip_id = self.tooltip_id;
|
||||||
move |_: &MouseMoveEvent, phase, cx| {
|
move |_: &MouseMoveEvent, phase, cx| {
|
||||||
let is_hovered =
|
let is_hovered =
|
||||||
pending_mouse_down.borrow().is_none() && hitbox.is_hovered(cx);
|
pending_mouse_down.borrow().is_none() && hitbox.is_hovered(cx);
|
||||||
if !is_hovered {
|
let tooltip_is_hovered =
|
||||||
active_tooltip.borrow_mut().take();
|
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
|
||||||
|
if !is_hovered && (!tooltip_is_hoverable || !tooltip_is_hovered) {
|
||||||
|
if active_tooltip.borrow_mut().take().is_some() {
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1833,15 +1880,14 @@ impl Interactivity {
|
||||||
if active_tooltip.borrow().is_none() {
|
if active_tooltip.borrow().is_none() {
|
||||||
let task = cx.spawn({
|
let task = cx.spawn({
|
||||||
let active_tooltip = active_tooltip.clone();
|
let active_tooltip = active_tooltip.clone();
|
||||||
let tooltip_builder = tooltip_builder.clone();
|
let build_tooltip = tooltip_builder.build.clone();
|
||||||
|
|
||||||
move |mut cx| async move {
|
move |mut cx| async move {
|
||||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
active_tooltip.borrow_mut().replace(ActiveTooltip {
|
active_tooltip.borrow_mut().replace(ActiveTooltip {
|
||||||
tooltip: Some(AnyTooltip {
|
tooltip: Some(AnyTooltip {
|
||||||
view: tooltip_builder(cx),
|
view: build_tooltip(cx),
|
||||||
cursor_offset: cx.mouse_position(),
|
mouse_position: cx.mouse_position(),
|
||||||
}),
|
}),
|
||||||
_task: None,
|
_task: None,
|
||||||
});
|
});
|
||||||
|
@ -1860,15 +1906,30 @@ impl Interactivity {
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let active_tooltip = active_tooltip.clone();
|
let active_tooltip = active_tooltip.clone();
|
||||||
move |_: &MouseDownEvent, _, _| {
|
let tooltip_id = self.tooltip_id;
|
||||||
active_tooltip.borrow_mut().take();
|
move |_: &MouseDownEvent, _, cx| {
|
||||||
|
let tooltip_is_hovered =
|
||||||
|
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
|
||||||
|
|
||||||
|
if !tooltip_is_hoverable || !tooltip_is_hovered {
|
||||||
|
if active_tooltip.borrow_mut().take().is_some() {
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.on_mouse_event({
|
cx.on_mouse_event({
|
||||||
let active_tooltip = active_tooltip.clone();
|
let active_tooltip = active_tooltip.clone();
|
||||||
move |_: &ScrollWheelEvent, _, _| {
|
let tooltip_id = self.tooltip_id;
|
||||||
active_tooltip.borrow_mut().take();
|
move |_: &ScrollWheelEvent, _, cx| {
|
||||||
|
let tooltip_is_hovered =
|
||||||
|
tooltip_id.map_or(false, |tooltip_id| tooltip_id.is_hovered(cx));
|
||||||
|
if !tooltip_is_hoverable || !tooltip_is_hovered {
|
||||||
|
if active_tooltip.borrow_mut().take().is_some() {
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -553,7 +553,7 @@ impl Element for InteractiveText {
|
||||||
ActiveTooltip {
|
ActiveTooltip {
|
||||||
tooltip: Some(AnyTooltip {
|
tooltip: Some(AnyTooltip {
|
||||||
view: tooltip,
|
view: tooltip,
|
||||||
cursor_offset: cx.mouse_position(),
|
mouse_position: cx.mouse_position(),
|
||||||
}),
|
}),
|
||||||
_task: None,
|
_task: None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -287,6 +287,8 @@ pub struct Window {
|
||||||
pub(crate) rendered_frame: Frame,
|
pub(crate) rendered_frame: Frame,
|
||||||
pub(crate) next_frame: Frame,
|
pub(crate) next_frame: Frame,
|
||||||
pub(crate) next_hitbox_id: HitboxId,
|
pub(crate) next_hitbox_id: HitboxId,
|
||||||
|
pub(crate) next_tooltip_id: TooltipId,
|
||||||
|
pub(crate) tooltip_bounds: Option<TooltipBounds>,
|
||||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||||
|
@ -551,6 +553,8 @@ impl Window {
|
||||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||||
next_frame_callbacks,
|
next_frame_callbacks,
|
||||||
next_hitbox_id: HitboxId::default(),
|
next_hitbox_id: HitboxId::default(),
|
||||||
|
next_tooltip_id: TooltipId::default(),
|
||||||
|
tooltip_bounds: None,
|
||||||
dirty_views: FxHashSet::default(),
|
dirty_views: FxHashSet::default(),
|
||||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||||
focus_listeners: SubscriberSet::new(),
|
focus_listeners: SubscriberSet::new(),
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
borrow::{Borrow, BorrowMut, Cow},
|
borrow::{Borrow, BorrowMut, Cow},
|
||||||
mem,
|
cmp, mem,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -28,17 +28,18 @@ use futures::{future::Shared, FutureExt};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use media::core_video::CVImageBuffer;
|
use media::core_video::CVImageBuffer;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use util::post_inc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
hash, prelude::*, size, AnyElement, AnyTooltip, AppContext, Asset, AvailableSpace, Bounds,
|
hash, point, prelude::*, px, size, AnyElement, AnyTooltip, AppContext, Asset, AvailableSpace,
|
||||||
BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchPhase,
|
Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchNodeId,
|
||||||
DispatchTree, DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId,
|
DispatchPhase, DispatchTree, DrawPhase, ElementId, ElementStateBox, EntityId, FocusHandle,
|
||||||
GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext, KeyEvent,
|
FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext,
|
||||||
LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite, MouseEvent, PaintQuad,
|
KeyEvent, LayoutId, LineLayoutIndex, ModifiersChangedEvent, MonochromeSprite, MouseEvent,
|
||||||
Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
|
PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
|
||||||
RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StrikethroughStyle,
|
RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size,
|
||||||
Style, Task, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, Window,
|
StrikethroughStyle, Style, Task, TextStyleRefinement, TransformationMatrix, Underline,
|
||||||
WindowContext, SUBPIXEL_VARIANTS,
|
UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) type AnyMouseListener =
|
pub(crate) type AnyMouseListener =
|
||||||
|
@ -84,6 +85,33 @@ impl Hitbox {
|
||||||
#[derive(Default, Eq, PartialEq)]
|
#[derive(Default, Eq, PartialEq)]
|
||||||
pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>);
|
pub(crate) struct HitTest(SmallVec<[HitboxId; 8]>);
|
||||||
|
|
||||||
|
/// An identifier for a tooltip.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct TooltipId(usize);
|
||||||
|
|
||||||
|
impl TooltipId {
|
||||||
|
/// Checks if the tooltip is currently hovered.
|
||||||
|
pub fn is_hovered(&self, cx: &WindowContext) -> bool {
|
||||||
|
cx.window
|
||||||
|
.tooltip_bounds
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |tooltip_bounds| {
|
||||||
|
tooltip_bounds.id == *self && tooltip_bounds.bounds.contains(&cx.mouse_position())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct TooltipBounds {
|
||||||
|
id: TooltipId,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct TooltipRequest {
|
||||||
|
id: TooltipId,
|
||||||
|
tooltip: AnyTooltip,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct DeferredDraw {
|
pub(crate) struct DeferredDraw {
|
||||||
priority: usize,
|
priority: usize,
|
||||||
parent_node: DispatchNodeId,
|
parent_node: DispatchNodeId,
|
||||||
|
@ -108,7 +136,7 @@ pub(crate) struct Frame {
|
||||||
pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
|
pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||||
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
|
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
|
||||||
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
|
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
|
||||||
pub(crate) tooltip_requests: Vec<Option<AnyTooltip>>,
|
pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
|
||||||
pub(crate) cursor_styles: Vec<CursorStyleRequest>,
|
pub(crate) cursor_styles: Vec<CursorStyleRequest>,
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
|
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
|
||||||
|
@ -364,6 +392,7 @@ impl<'a> VisualContext for ElementContext<'a> {
|
||||||
impl<'a> ElementContext<'a> {
|
impl<'a> ElementContext<'a> {
|
||||||
pub(crate) fn draw_roots(&mut self) {
|
pub(crate) fn draw_roots(&mut self) {
|
||||||
self.window.draw_phase = DrawPhase::Layout;
|
self.window.draw_phase = DrawPhase::Layout;
|
||||||
|
self.window.tooltip_bounds.take();
|
||||||
|
|
||||||
// Layout all root elements.
|
// Layout all root elements.
|
||||||
let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any();
|
let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any();
|
||||||
|
@ -388,14 +417,8 @@ impl<'a> ElementContext<'a> {
|
||||||
element.layout(offset, AvailableSpace::min_size(), self);
|
element.layout(offset, AvailableSpace::min_size(), self);
|
||||||
active_drag_element = Some(element);
|
active_drag_element = Some(element);
|
||||||
self.app.active_drag = Some(active_drag);
|
self.app.active_drag = Some(active_drag);
|
||||||
} else if let Some(tooltip_request) =
|
} else {
|
||||||
self.window.next_frame.tooltip_requests.last().cloned()
|
tooltip_element = self.layout_tooltip();
|
||||||
{
|
|
||||||
let tooltip_request = tooltip_request.unwrap();
|
|
||||||
let mut element = tooltip_request.view.clone().into_any();
|
|
||||||
let offset = tooltip_request.cursor_offset;
|
|
||||||
element.layout(offset, AvailableSpace::min_size(), self);
|
|
||||||
tooltip_element = Some(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position);
|
self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position);
|
||||||
|
@ -415,6 +438,52 @@ impl<'a> ElementContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_tooltip(&mut self) -> Option<AnyElement> {
|
||||||
|
let tooltip_request = self.window.next_frame.tooltip_requests.last().cloned()?;
|
||||||
|
let tooltip_request = tooltip_request.unwrap();
|
||||||
|
let mut element = tooltip_request.tooltip.view.clone().into_any();
|
||||||
|
let mouse_position = tooltip_request.tooltip.mouse_position;
|
||||||
|
let tooltip_size = element.measure(AvailableSpace::min_size(), self);
|
||||||
|
|
||||||
|
let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size);
|
||||||
|
let window_bounds = Bounds {
|
||||||
|
origin: Point::default(),
|
||||||
|
size: self.viewport_size(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if tooltip_bounds.right() > window_bounds.right() {
|
||||||
|
let new_x = mouse_position.x - tooltip_bounds.size.width - px(1.);
|
||||||
|
if new_x >= Pixels::ZERO {
|
||||||
|
tooltip_bounds.origin.x = new_x;
|
||||||
|
} else {
|
||||||
|
tooltip_bounds.origin.x = cmp::max(
|
||||||
|
Pixels::ZERO,
|
||||||
|
tooltip_bounds.origin.x - tooltip_bounds.right() - window_bounds.right(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tooltip_bounds.bottom() > window_bounds.bottom() {
|
||||||
|
let new_y = mouse_position.y - tooltip_bounds.size.height - px(1.);
|
||||||
|
if new_y >= Pixels::ZERO {
|
||||||
|
tooltip_bounds.origin.y = new_y;
|
||||||
|
} else {
|
||||||
|
tooltip_bounds.origin.y = cmp::max(
|
||||||
|
Pixels::ZERO,
|
||||||
|
tooltip_bounds.origin.y - tooltip_bounds.bottom() - window_bounds.bottom(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.after_layout(cx));
|
||||||
|
|
||||||
|
self.window.tooltip_bounds = Some(TooltipBounds {
|
||||||
|
id: tooltip_request.id,
|
||||||
|
bounds: tooltip_bounds,
|
||||||
|
});
|
||||||
|
Some(element)
|
||||||
|
}
|
||||||
|
|
||||||
fn layout_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
|
fn layout_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
|
||||||
assert_eq!(self.window.element_id_stack.len(), 0);
|
assert_eq!(self.window.element_id_stack.len(), 0);
|
||||||
|
|
||||||
|
@ -604,8 +673,13 @@ impl<'a> ElementContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a tooltip to be rendered for the upcoming frame
|
/// Sets a tooltip to be rendered for the upcoming frame
|
||||||
pub fn set_tooltip(&mut self, tooltip: AnyTooltip) {
|
pub fn set_tooltip(&mut self, tooltip: AnyTooltip) -> TooltipId {
|
||||||
self.window.next_frame.tooltip_requests.push(Some(tooltip));
|
let id = TooltipId(post_inc(&mut self.window.next_tooltip_id.0));
|
||||||
|
self.window
|
||||||
|
.next_frame
|
||||||
|
.tooltip_requests
|
||||||
|
.push(Some(TooltipRequest { id, tooltip }));
|
||||||
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes the given element id onto the global stack and invokes the given closure
|
/// Pushes the given element id onto the global stack and invokes the given closure
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use gpui::{anchored, Action, AnyView, IntoElement, Render, VisualContext};
|
use gpui::{Action, AnyView, IntoElement, Render, VisualContext};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
|
||||||
|
@ -90,18 +90,17 @@ pub fn tooltip_container<V>(
|
||||||
f: impl FnOnce(Div, &mut ViewContext<V>) -> Div,
|
f: impl FnOnce(Div, &mut ViewContext<V>) -> Div,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
|
||||||
// padding to avoid mouse cursor
|
|
||||||
anchored().child(
|
// padding to avoid tooltip appearing right below the mouse cursor
|
||||||
div().pl_2().pt_2p5().child(
|
div().pl_2().pt_2p5().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.text_ui()
|
.text_ui()
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.py_1()
|
.py_1()
|
||||||
.px_2()
|
.px_2()
|
||||||
.map(|el| f(el, cx)),
|
.map(|el| f(el, cx)),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue