use std::rc::Rc;
use gpui::{
App, Bounds, ClickEvent, Corners, ElementId, InteractiveElement as _, IntoElement,
ParentElement as _, Pixels, RenderOnce, SharedString, Styled as _, Window, canvas, deferred,
div, prelude::FluentBuilder as _, px,
};
use gpui_base::{Placement, Positioner};
use super::handle::SurfaceHandler;
use crate::{
ActiveTheme as _, Sizable as _, ThemeStyled as _,
button::{Button, ButtonCustomVariant, ButtonVariants as _},
h_flex,
separator::Separator,
};
use gpui_base::TestSupportExt as _;
const ROW_HEIGHT: Pixels = px(32.);
pub(crate) struct EditMenuItem {
label: SharedString,
on_click: Rc<dyn Fn(&mut Window, &mut App)>,
}
impl EditMenuItem {
pub(crate) fn new(
label: impl Into<SharedString>,
on_click: impl Fn(&mut Window, &mut App) + 'static,
) -> Self {
Self {
label: label.into(),
on_click: Rc::new(on_click),
}
}
}
#[derive(IntoElement)]
pub(crate) struct EditMenu {
id: ElementId,
anchor: Bounds<Pixels>,
items: Vec<EditMenuItem>,
on_paint: Option<SurfaceHandler>,
}
impl EditMenu {
pub(crate) fn new(id: impl Into<ElementId>, anchor: Bounds<Pixels>) -> Self {
Self {
id: id.into(),
anchor,
items: Vec::new(),
on_paint: None,
}
}
pub(crate) fn items(mut self, items: impl IntoIterator<Item = EditMenuItem>) -> Self {
self.items.extend(items);
self
}
pub(crate) fn on_paint(mut self, on_paint: SurfaceHandler) -> Self {
self.on_paint = Some(on_paint);
self
}
}
impl RenderOnce for EditMenu {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let id = self.id;
let on_paint = self.on_paint;
let radius = cx.theme().radius_lg;
let item_style = ButtonCustomVariant::new(cx)
.color(cx.theme().transparent)
.foreground(cx.theme().popover_foreground)
.hover(cx.theme().accent)
.active(cx.theme().accent);
let last = self.items.len().saturating_sub(1);
let items = self.items.into_iter().enumerate().flat_map(|(ix, item)| {
let on_click = item.on_click;
let corners = Corners {
top_left: ix == 0,
bottom_left: ix == 0,
top_right: ix == last,
bottom_right: ix == last,
};
let button = div()
.id(item.label.clone())
.test_support()
.child(
Button::new(ix)
.custom(item_style)
.large()
.h(ROW_HEIGHT)
.px_3()
.rounded(radius)
.border_corners(corners)
.tab_stop(false)
.label(item.label)
.on_click(move |_: &ClickEvent, window, cx| on_click(window, cx)),
)
.into_any_element();
(ix > 0)
.then(|| Separator::vertical().into_any_element())
.into_iter()
.chain([button])
});
deferred(
Positioner::side(self.anchor)
.placement(Placement::Top)
.offset(px(8.))
.occlude()
.child(
h_flex()
.id(id)
.relative()
.items_stretch()
.overflow_hidden()
.popover_style(cx)
.rounded(radius)
.children(items)
.when_some(on_paint, |this, on_paint| {
this.child(
canvas(
move |bounds, window, cx| on_paint(bounds, window, cx),
|_, _, _, _| {},
)
.absolute()
.inset_0(),
)
}),
),
)
.with_priority(gpui_base::POPUP_PRIORITY)
}
}