use super::*;
use gpui::ClickEvent;
const DRAG_SLOP: f64 = 2.0;
pub(super) fn image(
url: &str,
alt: &Text,
width: Option<u32>,
overlay: Overlay,
typography: &Typography,
theme: &Theme,
cx: &App,
) -> AnyElement {
let hint = SharedString::new_static(CAPTION_HINT);
let overlay = Overlay {
placeholder: Some(&hint),
..overlay.at(Part::Caption)
};
let picture = if url.is_empty() {
div()
.h(px(IMAGE_EMPTY_HEIGHT))
.flex()
.items_center()
.px(px(CARD_PADDING))
.rounded(px(Theme::button_radius()))
.border_1()
.border_dashed()
.border_color(theme.border)
.text_size(px(typography.body.size()))
.text_color(theme.text_muted)
.child(IMAGE_EMPTY)
.into_any_element()
} else {
let picture = img(image_source(url, overlay.base));
let ix = overlay.block;
let box_ = div()
.id(ElementId::named_usize("md-picture", ix))
.relative()
.rounded(px(Theme::button_radius()))
.overflow_hidden()
.border_1()
.border_color(theme.border)
.when_some(overlay.image.cloned(), |el, on_image| {
el.cursor_pointer().on_click(move |event, window, cx| {
if let ClickEvent::Mouse(click) = event
&& (click.up.position - click.down.position).magnitude() > DRAG_SLOP
{
return;
}
on_image(ix, window, cx);
})
})
.children(overlay.layouts.map(|layouts| {
let layouts = layouts.clone();
canvas(
move |bounds, _, _| layouts.record_picture(ix, bounds),
|_, _, _, _| (),
)
.absolute()
.size_full()
}));
match width {
Some(width) => box_
.self_start()
.max_w_full()
.w(px(width as f32))
.child(picture.w(px(width as f32)).max_w_full()),
None => box_.child(picture.max_w_full()),
}
.into_any_element()
};
div()
.flex()
.flex_col()
.gap(px(CAPTION_GAP))
.child(picture)
.when(
overlay.caption == Caption::Shown && (!alt.is_empty() || overlay.caret().is_some()),
|el| {
el.child(text_element(
alt,
typography.caption.size(),
typography.caption.line_height(),
FontWeight::NORMAL,
overlay,
theme,
cx,
))
},
)
.into_any_element()
}
pub(super) fn bookmark(
ix: usize,
url: &str,
form: Form,
typography: &Typography,
theme: &Theme,
cx: &App,
) -> AnyElement {
let preview = preview::of(cx, url).unwrap_or_default();
let host = SharedString::from(preview::host(url).to_string());
let label = preview.label.clone().unwrap_or_else(|| host.clone());
let title = preview
.title
.clone()
.unwrap_or_else(|| SharedString::from(url.to_string()));
let (icon, muted, wash) = (preview.icon.clone(), theme.text_muted, theme.element_hover);
let site = host.clone();
let mark = move |size: f32| {
let host = site.clone();
match icon.clone() {
Some(icon) => img(icon)
.size(px(size))
.rounded(px(size / 4.0))
.with_fallback(move || initial(&host, size, muted, wash))
.into_any_element(),
None => initial(&host, size, muted, wash),
}
};
let words = div()
.flex()
.flex_col()
.min_w_0()
.px(px(CARD_PADDING))
.py(px(CARD_PADDING - 2.0))
.child(
div()
.truncate()
.text_size(px(typography.body.size()))
.line_height(px(typography.body.line_height()))
.text_color(theme.text)
.child(title),
)
.children(preview.description.map(|blurb| {
div()
.line_clamp(2)
.text_size(px(typography.card.size()))
.line_height(px(typography.card.line_height()))
.text_color(theme.text_muted)
.child(blurb)
}))
.child(
div()
.mt_auto()
.pt(px(6.0))
.flex()
.items_center()
.gap(px(6.0))
.text_size(px(typography.card.size()))
.text_color(theme.text_muted)
.child(mark(CARD_ICON))
.child(div().truncate().child(label)),
);
let picture = corners(div(), form)
.bg(theme.surface)
.flex()
.items_center()
.justify_center()
.overflow_hidden()
.child(match preview.image {
Some(image) => corners(img(image).size_full().object_fit(ObjectFit::Cover), form)
.with_fallback(move || mark(CARD_COVER))
.into_any_element(),
None => mark(CARD_COVER),
});
let open = url.to_string();
let card = div()
.id(ElementId::named_usize("md-bookmark", ix))
.flex()
.w_full()
.overflow_hidden()
.rounded(px(Theme::button_radius()))
.border(px(CARD_BORDER))
.border_color(theme.border)
.bg(theme.surface_card)
.cursor(CursorStyle::PointingHand)
.hover(|el| el.bg(theme.element_hover))
.on_click(move |_, _, cx| cx.open_url(&open));
if form == Form::Embed {
card.flex_col()
.child(picture.w_full().h(px(CARD_COVER_HEIGHT)))
.child(words.w_full())
} else {
card.h(px(CARD_HEIGHT))
.child(words.flex_1())
.child(picture.flex_none().w(px(CARD_IMAGE_WIDTH)).h_full())
}
.into_any_element()
}
pub(super) fn corners<T: Styled>(element: T, form: Form) -> T {
let corner = px(Theme::inset_radius(Theme::button_radius(), CARD_BORDER));
match form {
Form::Embed => element.rounded_t(corner),
_ => element.rounded_r(corner),
}
}
pub(super) fn initial(host: &str, size: f32, color: Hsla, wash: Hsla) -> AnyElement {
div()
.flex_none()
.size(px(size))
.rounded(px(size / 4.0))
.bg(wash)
.flex()
.items_center()
.justify_center()
.text_size(px(size * 0.55))
.text_color(color)
.child(SharedString::from(
host.chars()
.next()
.unwrap_or('?')
.to_uppercase()
.to_string(),
))
.into_any_element()
}
pub(super) struct MentionCard {
url: String,
typography: Typography,
}
impl MentionCard {
pub(super) fn view(url: &str, cx: &mut App) -> gpui::AnyView {
let typography = Typography::of(cx);
cx.new(|_| Self {
url: url.to_string(),
typography,
})
.into()
}
}
impl Render for MentionCard {
fn render(&mut self, _: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement {
let theme = Theme::of(cx).clone();
div().w(px(MENTION_CARD_WIDTH)).child(bookmark(
0,
&self.url,
Form::Embed,
&self.typography,
&theme,
cx,
))
}
}