- Fix executor.timer() in gpui2
- Add support for tooltips 

[[PR Description]]

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2023-11-06 10:51:01 -07:00 committed by GitHub
commit 3afdeeac1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 80 additions and 49 deletions

View file

@ -20,6 +20,7 @@ fn generate_dispatch_bindings() {
.header("src/platform/mac/dispatch.h") .header("src/platform/mac/dispatch.h")
.allowlist_var("_dispatch_main_q") .allowlist_var("_dispatch_main_q")
.allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT") .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
.allowlist_var("DISPATCH_TIME_NOW")
.allowlist_function("dispatch_get_global_queue") .allowlist_function("dispatch_get_global_queue")
.allowlist_function("dispatch_async_f") .allowlist_function("dispatch_async_f")
.allowlist_function("dispatch_after_f") .allowlist_function("dispatch_after_f")

View file

@ -157,7 +157,7 @@ pub struct AppContext {
flushing_effects: bool, flushing_effects: bool,
pending_updates: usize, pending_updates: usize,
pub(crate) active_drag: Option<AnyDrag>, pub(crate) active_drag: Option<AnyDrag>,
pub(crate) active_tooltip: Option<AnyView>, pub(crate) active_tooltip: Option<AnyTooltip>,
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>, pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>, pub(crate) frame_consumers: HashMap<DisplayId, Task<()>>,
pub(crate) background_executor: BackgroundExecutor, pub(crate) background_executor: BackgroundExecutor,
@ -898,3 +898,9 @@ pub(crate) struct AnyDrag {
pub view: AnyView, pub view: AnyView,
pub cursor_offset: Point<Pixels>, pub cursor_offset: Point<Pixels>,
} }
#[derive(Clone)]
pub(crate) struct AnyTooltip {
pub view: AnyView,
pub cursor_offset: Point<Pixels>,
}

View file

@ -21,7 +21,7 @@ pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
} }
impl<T: Clone + Debug + Default> Point<T> { impl<T: Clone + Debug + Default> Point<T> {
pub fn new(x: T, y: T) -> Self { pub const fn new(x: T, y: T) -> Self {
Self { x, y } Self { x, y }
} }

View file

@ -1,8 +1,8 @@
use crate::{ use crate::{
div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component, div, point, px, Action, AnyDrag, AnyTooltip, AnyView, AppContext, BorrowWindow, Bounds,
DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Component, DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch,
Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View, Keystroke, Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style,
ViewContext, StyleRefinement, Task, View, ViewContext,
}; };
use collections::HashMap; use collections::HashMap;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
@ -17,9 +17,12 @@ use std::{
ops::Deref, ops::Deref,
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
time::Duration,
}; };
const DRAG_THRESHOLD: f64 = 2.; const DRAG_THRESHOLD: f64 = 2.;
const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
pub trait StatelessInteractive<V: 'static>: Element<V> { pub trait StatelessInteractive<V: 'static>: Element<V> {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>; fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
@ -601,38 +604,72 @@ pub trait ElementInteraction<V: 'static>: 'static {
if let Some(hover_listener) = stateful.hover_listener.take() { if let Some(hover_listener) = stateful.hover_listener.take() {
let was_hovered = element_state.hover_state.clone(); let was_hovered = element_state.hover_state.clone();
let has_mouse_down = element_state.pending_mouse_down.lock().is_some(); let has_mouse_down = element_state.pending_mouse_down.clone();
let active_tooltip = element_state.active_tooltip.clone();
let tooltip_builder = stateful.tooltip_builder.clone();
cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble { if phase != DispatchPhase::Bubble {
return; return;
} }
let is_hovered = bounds.contains_point(&event.position) && !has_mouse_down; let is_hovered =
bounds.contains_point(&event.position) && has_mouse_down.lock().is_none();
let mut was_hovered = was_hovered.lock(); let mut was_hovered = was_hovered.lock();
if is_hovered != was_hovered.clone() { if is_hovered != was_hovered.clone() {
*was_hovered = is_hovered; *was_hovered = is_hovered;
drop(was_hovered); drop(was_hovered);
if let Some(tooltip_builder) = &tooltip_builder {
let mut active_tooltip = active_tooltip.lock();
if is_hovered && active_tooltip.is_none() {
*active_tooltip = Some(tooltip_builder(view_state, cx));
} else if !is_hovered {
active_tooltip.take();
}
}
hover_listener(view_state, is_hovered, cx); hover_listener(view_state, is_hovered, cx);
} }
}); });
} }
if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { if let Some(tooltip_builder) = stateful.tooltip_builder.take() {
if *element_state.hover_state.lock() { let active_tooltip = element_state.active_tooltip.clone();
cx.active_tooltip = Some(active_tooltip.clone()); let pending_mouse_down = element_state.pending_mouse_down.clone();
cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
if phase != DispatchPhase::Bubble {
return;
}
let is_hovered = bounds.contains_point(&event.position)
&& pending_mouse_down.lock().is_none();
if !is_hovered {
active_tooltip.lock().take();
return;
}
if active_tooltip.lock().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |view, mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
view.update(&mut cx, move |view_state, cx| {
active_tooltip.lock().replace(ActiveTooltip {
waiting: None,
tooltip: Some(AnyTooltip {
view: tooltip_builder(view_state, cx),
cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET,
}),
});
cx.notify();
})
.ok();
}
});
active_tooltip.lock().replace(ActiveTooltip {
waiting: Some(task),
tooltip: None,
});
}
});
if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() {
if active_tooltip.tooltip.is_some() {
cx.active_tooltip = active_tooltip.tooltip.clone()
}
} }
} }
@ -823,7 +860,13 @@ pub struct InteractiveElementState {
hover_state: Arc<Mutex<bool>>, hover_state: Arc<Mutex<bool>>,
pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>, pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>, scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
active_tooltip: Arc<Mutex<Option<AnyView>>>, active_tooltip: Arc<Mutex<Option<ActiveTooltip>>>,
}
struct ActiveTooltip {
#[allow(unused)] // used to drop the task
waiting: Option<Task<()>>,
tooltip: Option<AnyTooltip>,
} }
impl InteractiveElementState { impl InteractiveElementState {

View file

@ -11,11 +11,7 @@ use objc::{
}; };
use parking::{Parker, Unparker}; use parking::{Parker, Unparker};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{ffi::c_void, sync::Arc, time::Duration};
ffi::c_void,
sync::Arc,
time::{Duration, SystemTime},
};
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs")); include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
@ -62,16 +58,10 @@ impl PlatformDispatcher for MacDispatcher {
} }
fn dispatch_after(&self, duration: Duration, runnable: Runnable) { fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
let now = SystemTime::now();
let after_duration = now
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
+ duration.as_nanos() as u64;
unsafe { unsafe {
let queue = let queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0); dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
let when = dispatch_time(0, after_duration as i64); let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
dispatch_after_f( dispatch_after_f(
when, when,
queue, queue,

View file

@ -989,10 +989,10 @@ impl<'a> WindowContext<'a> {
}); });
} else if let Some(active_tooltip) = self.app.active_tooltip.take() { } else if let Some(active_tooltip) = self.app.active_tooltip.take() {
self.stack(1, |cx| { self.stack(1, |cx| {
cx.with_element_offset(Some(cx.mouse_position()), |cx| { cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| {
let available_space = let available_space =
size(AvailableSpace::MinContent, AvailableSpace::MinContent); size(AvailableSpace::MinContent, AvailableSpace::MinContent);
active_tooltip.draw(available_space, cx); active_tooltip.view.draw(available_space, cx);
}); });
}); });
} }

View file

@ -1,6 +1,4 @@
use gpui2::{ use gpui2::{div, px, Div, ParentElement, Render, SharedString, Styled, ViewContext};
div, px, Div, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext,
};
use theme2::ActiveTheme; use theme2::ActiveTheme;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -12,10 +10,6 @@ impl TextTooltip {
pub fn new(str: SharedString) -> Self { pub fn new(str: SharedString) -> Self {
Self { title: str } Self { title: str }
} }
pub fn build_view<C: VisualContext>(str: SharedString, cx: &mut C) -> C::Result<View<Self>> {
cx.build_view(|cx| TextTooltip::new(str))
}
} }
impl Render for TextTooltip { impl Render for TextTooltip {
@ -27,6 +21,7 @@ impl Render for TextTooltip {
.bg(theme.colors().background) .bg(theme.colors().background)
.rounded(px(8.)) .rounded(px(8.))
.border() .border()
.font("Zed Sans")
.border_color(theme.colors().border) .border_color(theme.colors().border)
.text_color(theme.colors().text) .text_color(theme.colors().text)
.pl_2() .pl_2()

View file

@ -1395,13 +1395,9 @@ impl Pane {
.group("") .group("")
.id(item.id()) .id(item.id())
.cursor_pointer() .cursor_pointer()
.on_hover(|_, hovered, _| {
dbg!(hovered);
})
.when_some(item.tab_tooltip_text(cx), |div, text| { .when_some(item.tab_tooltip_text(cx), |div, text| {
div.tooltip(move |_, cx| TextTooltip::build_view(text.clone(), cx)) div.tooltip(move |_, cx| cx.build_view(|cx| TextTooltip::new(text.clone())))
}) })
// .tooltip(|pane, cx| cx.build_view(|cx| div().child(title)))
// .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx)) // .on_drag(move |pane, cx| pane.render_tab(ix, item.boxed_clone(), detail, cx))
// .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target)) // .drag_over::<DraggedTab>(|d| d.bg(cx.theme().colors().element_drop_target))
// .on_drop(|_view, state: View<DraggedTab>, cx| { // .on_drop(|_view, state: View<DraggedTab>, cx| {