use crate::gpui_compat::element_id;
use crate::motion::{fade_in, pop_in};
use gpui::{
AnyElement, App, Context, IntoElement, KeyBinding, MouseButton, Render, SharedString, Window,
actions, div, prelude::*, px,
};
use liora_core::Config;
use liora_icons::Icon;
use liora_icons_lucide::IconName;
use std::sync::Arc;
actions!(dialog, [DialogClose]);
pub struct Dialog {
id: SharedString,
title: SharedString,
content: Arc<dyn Fn(&mut Window, &mut Context<DialogView>) -> AnyElement + 'static>,
close_on_click_outside: bool,
close_on_escape: bool,
}
pub struct DialogView {
id: SharedString,
title: SharedString,
content: Arc<dyn Fn(&mut Window, &mut Context<Self>) -> AnyElement + 'static>,
close_on_click_outside: bool,
close_on_escape: bool,
on_close: Arc<dyn Fn(&mut Window, &mut App) + 'static>,
}
impl DialogView {
fn new(
id: SharedString,
title: SharedString,
content: Arc<dyn Fn(&mut Window, &mut Context<Self>) -> AnyElement + 'static>,
close_on_click_outside: bool,
close_on_escape: bool,
on_close: impl Fn(&mut Window, &mut App) + 'static,
) -> Self {
Self {
id,
title,
content,
close_on_click_outside,
close_on_escape,
on_close: Arc::new(on_close),
}
}
}
impl Render for DialogView {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.global::<Config>().theme.clone();
let id = self.id.clone();
let title = self.title.clone();
let content_fn = self.content.clone();
let on_close = self.on_close.clone();
let close_on_click_outside = self.close_on_click_outside;
let close_on_escape = self.close_on_escape;
fade_in(
element_id(format!("{id}-overlay-motion")),
div()
.id(id.clone())
.absolute()
.size_full()
.cursor_default()
.bg(theme.neutral.overlay)
.flex()
.items_center()
.justify_center()
.on_mouse_move(|_, _, cx| {
cx.stop_propagation();
})
.when(close_on_click_outside, |s| {
s.on_mouse_down(MouseButton::Left, {
let on_close = on_close.clone();
move |_, window, cx| {
on_close(window, cx);
}
})
})
.when(close_on_escape, |s| {
s.on_action(cx.listener({
let on_close = on_close.clone();
move |_, _action: &DialogClose, window, cx| {
on_close(window, cx);
}
}))
})
.child(pop_in(
element_id(format!("{id}-panel-motion")),
div()
.w(px(400.0))
.bg(theme.neutral.card)
.cursor_default()
.rounded(px(theme.radius.md))
.shadow_xl()
.on_mouse_move(|_, _, cx| {
cx.stop_propagation();
})
.on_mouse_down(MouseButton::Left, |_, _, cx| {
cx.stop_propagation();
}) .child(
div()
.p_4()
.border_b_1()
.border_color(theme.neutral.border)
.flex()
.justify_between()
.items_center()
.child(div().font_weight(gpui::FontWeight::BOLD).child(title))
.child(
div()
.id(element_id(format!("{id}-close-btn")))
.cursor_pointer()
.child(
Icon::new(IconName::X)
.size(px(16.0))
.color(theme.neutral.icon),
)
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
on_close(window, cx);
}),
),
)
.child(div().p_4().child(content_fn(_window, cx))),
)),
)
}
}
#[cfg(test)]
mod motion_tests {
#[test]
fn dialog_uses_liora_motion_on_overlay_and_panel() {
let source = include_str!("dialog.rs")
.split("#[cfg(test)]")
.next()
.unwrap();
assert!(source.contains("fade_in("));
assert!(source.contains("pop_in("));
assert!(source.contains("panel-motion"));
}
}
impl Dialog {
pub fn register_key_bindings(cx: &mut App) {
cx.bind_keys([KeyBinding::new("escape", DialogClose, None)]);
}
pub fn new() -> Self {
Self {
id: liora_core::unique_id("dialog"),
title: SharedString::default(),
content: Arc::new(|_, _| div().child("Dialog Content").into_any_element()),
close_on_click_outside: true,
close_on_escape: true,
}
}
pub fn id(mut self, id: impl Into<SharedString>) -> Self {
self.id = id.into();
self
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = title.into();
self
}
pub fn close_on_click_outside(mut self, c: bool) -> Self {
self.close_on_click_outside = c;
self
}
pub fn close_on_escape(mut self, c: bool) -> Self {
self.close_on_escape = c;
self
}
pub fn content<F, E>(mut self, f: F) -> Self
where
F: Fn(&mut Window, &mut Context<DialogView>) -> E + 'static,
E: IntoElement,
{
self.content = Arc::new(move |window, cx| f(window, cx).into_any_element());
self
}
pub fn show(self, cx: &mut App) {
let id = self.id;
let title = self.title;
let content = self.content;
let close_on_click_outside = self.close_on_click_outside;
let close_on_escape = self.close_on_escape;
let id_for_close = id.clone();
let view = cx.new(|_cx| {
DialogView::new(
id.clone(),
title,
content,
close_on_click_outside,
close_on_escape,
move |_window, _cx| {
liora_core::clear_modal(&id_for_close, _cx);
},
)
});
liora_core::set_active_modal(id, view.into(), cx);
}
pub fn close(cx: &mut App) {
liora_core::clear_active_modal(cx);
}
pub fn close_id(id: impl Into<SharedString>, cx: &mut App) {
let id = id.into();
liora_core::clear_modal(&id, cx);
}
}