Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions crates/macros/src/derive_into_plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,22 @@ pub fn derive_into_plot(input: TokenStream) -> TokenStream {
cx: &mut gpui::App,
) -> Self::PrepaintState {
// No id => tooltips disabled => behave exactly like a non-interactive plot.
let Some(global_id) = global_id else {
return None;
};
let global_id = global_id?;

// Read the cursor position recorded by the previous frame's mouse handler.
let Some(position) = Self::__plot_tooltip_cursor(global_id, window).get() else {
return None;
};
let Some(state) = <Self as Plot>::tooltip_state(self, position, bounds, cx)
else {
return None;
};
let position = Self::__plot_tooltip_cursor(global_id, window).get()?;
let state = <Self as Plot>::tooltip_state(self, position, bounds, cx)?;

// Pass the live cursor so the tooltip box can follow it; the crosshair and
// dots in `state` stay snapped to the data point by `tooltip_state`.
let Some(mut overlay) =
<Self as Plot>::tooltip(self, &state, position, bounds, window, cx)
else {
return None;
};

let overlay = <Self as Plot>::tooltip(self, &state, position, bounds, window, cx)?;

// Defer the overlay so it paints above sibling content drawn after the plot
// (e.g. a chart card's footer text). The tooltip box can extend past the plot
// bounds; without deferral those later siblings would cover the overflow.
let mut overlay = gpui::IntoElement::into_any_element(
gpui::deferred(overlay),
);
overlay.prepaint_as_root(bounds.origin, bounds.size.into(), window, cx);
Some(overlay)
}
Expand Down
55 changes: 38 additions & 17 deletions crates/ui/src/plot/tooltip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ impl CrossLineAxis {
#[derive(IntoElement)]
pub struct CrossLine {
point: Point<Pixels>,
/// Start offset along the cross axis (vertical line: y; horizontal line: x).
start: f32,
/// Length along the cross axis; `None` spans the full extent.
length: Option<f32>,
/// Span `(start, length)` of the vertical line along the y axis; `length` of `None`
/// spans the full height.
vertical: (f32, Option<f32>),
/// Span `(start, length)` of the horizontal line along the x axis; `length` of `None`
/// spans the full width.
horizontal: (f32, Option<f32>),
/// Band thickness perpendicular to the line (solid band mode only).
thickness: Pixels,
/// `true` (default) draws a dashed hairline; `false` a solid band of `thickness`.
Expand All @@ -45,8 +47,8 @@ impl CrossLine {
pub fn new(point: Point<Pixels>) -> Self {
Self {
point,
start: 0.,
length: None,
vertical: (0., None),
horizontal: (0., None),
thickness: px(1.),
dashed: true,
direction: Default::default(),
Expand Down Expand Up @@ -74,17 +76,29 @@ impl CrossLine {
self
}

/// Set the length of the cross line along its axis (from the start edge).
/// Set the vertical line's length along the y axis (from the top edge).
pub fn height(mut self, height: f32) -> Self {
self.length = Some(height);
self.vertical.1 = Some(height);
self
}

/// Confine the cross line to `[start, start + length]` along its axis (vertical
/// line: y; horizontal line: x), so it stays within the plot area.
/// Set the horizontal line's length along the x axis (from the left edge).
pub fn width(mut self, width: f32) -> Self {
self.horizontal.1 = Some(width);
self
}

/// Confine the vertical line to `[start, start + length]` along the y axis, so it
/// stays within the plot area.
pub fn span(mut self, start: f32, length: f32) -> Self {
self.start = start;
self.length = Some(length);
self.vertical = (start, Some(length));
self
}

/// Confine the horizontal line to `[start, start + length]` along the x axis, so it
/// stays within the plot area.
pub fn h_span(mut self, start: f32, length: f32) -> Self {
self.horizontal = (start, Some(length));
self
}
}
Expand All @@ -107,21 +121,28 @@ impl CrossLine {
};
// The dashed hairline is a zero-width strip drawn entirely by its 1px border.
let thickness = if self.dashed { px(0.) } else { self.thickness };
// Each axis carries its own span so a `both` crosshair can confine the vertical
// and horizontal lines independently.
let (start, length) = if vertical {
self.vertical
} else {
self.horizontal
};

let el = div().absolute();
let el = if vertical {
el.left(self.point.x - thickness * 0.5)
.w(thickness)
.top(px(self.start))
.map(|el| match self.length {
.top(px(start))
.map(|el| match length {
Some(length) => el.h(px(length)),
None => el.h_full(),
})
} else {
el.top(self.point.y - thickness * 0.5)
.h(thickness)
.left(px(self.start))
.map(|el| match self.length {
.left(px(start))
.map(|el| match length {
Some(length) => el.w(px(length)),
None => el.w_full(),
})
Expand Down Expand Up @@ -257,7 +278,7 @@ impl Tooltip {
/// Create a tooltip whose box follows the cursor at `cursor` within a `within`-sized plot.
pub fn new(cursor: Point<Pixels>, within: Size<Pixels>) -> Self {
Self {
base: v_flex().top_0(),
base: v_flex(),
gap: px(0.),
cross_line: None,
dots: None,
Expand Down
Loading