use gpui::{
div, prelude::FluentBuilder, px, AnyElement, App, Div, Hsla, IntoElement, ParentElement,
Pixels, Point, RenderOnce, StyleRefinement, Styled, Window,
};
use crate::{v_flex, ActiveTheme};
#[derive(Default)]
pub enum CrossLineAxis {
#[default]
Vertical,
Horizontal,
Both,
}
impl CrossLineAxis {
#[inline]
pub fn show_vertical(&self) -> bool {
matches!(self, CrossLineAxis::Vertical | CrossLineAxis::Both)
}
#[inline]
pub fn show_horizontal(&self) -> bool {
matches!(self, CrossLineAxis::Horizontal | CrossLineAxis::Both)
}
}
#[derive(IntoElement)]
pub struct CrossLine {
point: Point<Pixels>,
height: Option<f32>,
direction: CrossLineAxis,
}
impl CrossLine {
pub fn new(point: Point<Pixels>) -> Self {
Self {
point,
height: None,
direction: Default::default(),
}
}
pub fn horizontal(mut self) -> Self {
self.direction = CrossLineAxis::Horizontal;
self
}
pub fn both(mut self) -> Self {
self.direction = CrossLineAxis::Both;
self
}
pub fn height(mut self, height: f32) -> Self {
self.height = Some(height);
self
}
}
impl From<Point<Pixels>> for CrossLine {
fn from(value: Point<Pixels>) -> Self {
Self::new(value)
}
}
impl RenderOnce for CrossLine {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.size_full()
.absolute()
.top_0()
.left_0()
.when(self.direction.show_vertical(), |this| {
this.child(
div()
.absolute()
.w(px(1.))
.bg(cx.theme().border)
.top_0()
.left(self.point.x)
.map(|this| {
if let Some(height) = self.height {
this.h(px(height))
} else {
this.h_full()
}
}),
)
})
.when(self.direction.show_horizontal(), |this| {
this.child(
div()
.absolute()
.w_full()
.h(px(1.))
.bg(cx.theme().border)
.left_0()
.top(self.point.y),
)
})
}
}
#[derive(IntoElement)]
pub struct Dot {
point: Point<Pixels>,
size: Pixels,
stroke: Hsla,
fill: Hsla,
}
impl Dot {
pub fn new(point: Point<Pixels>) -> Self {
Self {
point,
size: px(6.),
stroke: gpui::transparent_black(),
fill: gpui::transparent_black(),
}
}
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = size.into();
self
}
pub fn stroke(mut self, stroke: Hsla) -> Self {
self.stroke = stroke;
self
}
pub fn fill(mut self, fill: Hsla) -> Self {
self.fill = fill;
self
}
}
impl RenderOnce for Dot {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
let border_width = px(1.);
let offset = self.size / 2. - border_width / 2.;
div()
.absolute()
.w(self.size)
.h(self.size)
.rounded_full()
.border(border_width)
.border_color(self.stroke)
.bg(self.fill)
.left(self.point.x - offset)
.top(self.point.y - offset)
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub enum TooltipPosition {
#[default]
Left,
Right,
}
#[derive(Clone)]
pub struct TooltipState {
pub index: usize,
pub cross_line: Point<Pixels>,
pub dots: Vec<Point<Pixels>>,
pub position: TooltipPosition,
}
impl TooltipState {
pub fn new(
index: usize,
cross_line: Point<Pixels>,
dots: Vec<Point<Pixels>>,
position: TooltipPosition,
) -> Self {
Self {
index,
cross_line,
dots,
position,
}
}
}
#[derive(IntoElement)]
pub struct Tooltip {
base: Div,
position: Option<TooltipPosition>,
gap: Pixels,
cross_line: Option<CrossLine>,
dots: Option<Vec<Dot>>,
appearance: bool,
}
impl Tooltip {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
base: v_flex().top_0(),
position: Default::default(),
gap: px(0.),
cross_line: None,
dots: None,
appearance: true,
}
}
pub fn position(mut self, position: TooltipPosition) -> Self {
self.position = Some(position);
self
}
pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
self.gap = gap.into();
self
}
pub fn cross_line(mut self, cross_line: CrossLine) -> Self {
self.cross_line = Some(cross_line);
self
}
pub fn dots(mut self, dots: impl IntoIterator<Item = Dot>) -> Self {
self.dots = Some(dots.into_iter().collect());
self
}
pub fn appearance(mut self, appearance: bool) -> Self {
self.appearance = appearance;
self
}
}
impl Styled for Tooltip {
fn style(&mut self) -> &mut StyleRefinement {
self.base.style()
}
}
impl ParentElement for Tooltip {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.base.extend(elements);
}
}
impl RenderOnce for Tooltip {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
div()
.size_full()
.absolute()
.top_0()
.left_0()
.when_some(self.cross_line, |this, cross_line| this.child(cross_line))
.when_some(self.dots, |this, dots| this.children(dots))
.child(self.base.map(|this| {
if self.appearance {
this.absolute()
.min_w(px(168.))
.p_2()
.border_1()
.border_color(cx.theme().border)
.rounded_sm()
.bg(cx.theme().background.opacity(0.9))
.when_some(self.position, |this, position| {
if position == TooltipPosition::Left {
this.left(self.gap)
} else {
this.right(self.gap)
}
})
} else {
this.size_full().relative()
}
}))
}
}