use egui::{FontId, Stroke, StrokeKind, Ui};
use super::button::{Button, ButtonSize};
use super::{alpha, corner};
use crate::{Elevation, RADIUS, SPACING, palette_of};
pub struct DialogControl {
close: bool,
}
impl DialogControl {
fn new() -> Self {
Self { close: false }
}
pub fn close(&mut self) {
self.close = true;
}
}
pub struct Dialog<'a> {
title: &'a str,
width: f32,
}
impl<'a> Dialog<'a> {
pub fn new(title: &'a str) -> Self {
Self {
title,
width: 420.0,
}
}
pub fn width(mut self, w: f32) -> Self {
self.width = w;
self
}
pub fn show(
self,
ctx: &egui::Context,
body: impl FnOnce(&mut Ui, &mut DialogControl),
actions: impl FnOnce(&mut Ui, &mut DialogControl),
) -> bool {
let palette = palette_of(ctx);
let screen = ctx.content_rect();
let scrim_layer = egui::LayerId::new(egui::Order::Background, egui::Id::new("sauge_scrim"));
let scrim_painter = ctx.layer_painter(scrim_layer);
scrim_painter.rect_filled(screen, corner(0.0), alpha(egui::Color32::BLACK, 0.35));
let title = self.title.to_string();
let width = self.width;
let mut control = DialogControl::new();
egui::Window::new(&title)
.collapsible(false)
.resizable(false)
.title_bar(false)
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
.default_width(width)
.frame(
egui::Frame::default()
.fill(palette.bg_surface)
.stroke(Stroke::new(1.0, palette.border_default))
.corner_radius(corner(RADIUS.lg))
.inner_margin(egui::Margin::same(SPACING.s4 as i8))
.shadow(Elevation::Modal.shadow(palette.dark_mode)),
)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label(
egui::RichText::new(&title)
.font(FontId::new(18.0, egui::FontFamily::Proportional))
.color(palette.text_primary),
);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let (rect, resp) =
ui.allocate_exact_size(egui::vec2(22.0, 22.0), egui::Sense::click());
if resp.hovered() {
ui.painter().rect(
rect,
corner(RADIUS.sm),
alpha(palette.text_primary, 0.08),
Stroke::NONE,
StrokeKind::Inside,
);
}
crate::Icon::Close.paint(
ui.painter(),
rect.shrink(5.0),
palette.text_secondary,
);
if resp.clicked() {
control.close();
}
});
});
ui.add_space(SPACING.s3);
let sep_y = ui.cursor().top();
ui.painter().line_segment(
[
egui::pos2(ui.min_rect().left(), sep_y),
egui::pos2(ui.min_rect().right(), sep_y),
],
Stroke::new(1.0, palette.border_subtle),
);
ui.add_space(SPACING.s3);
body(ui, &mut control);
ui.add_space(SPACING.s4);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
actions(ui, &mut control);
});
});
control.close
}
}
pub struct ConfirmDialog<'a> {
title: &'a str,
body: &'a str,
confirm_label: Option<&'a str>,
cancel_label: Option<&'a str>,
danger: bool,
}
impl<'a> ConfirmDialog<'a> {
pub fn new(title: &'a str, body: &'a str) -> Self {
Self {
title,
body,
confirm_label: None,
cancel_label: None,
danger: false,
}
}
pub fn danger(mut self) -> Self {
self.danger = true;
self
}
pub fn confirm_label(mut self, label: &'a str) -> Self {
self.confirm_label = Some(label);
self
}
pub fn cancel_label(mut self, label: &'a str) -> Self {
self.cancel_label = Some(label);
self
}
pub fn show(self, ctx: &egui::Context) -> Option<bool> {
use crate::theme::locale::{Key, tr};
let locale = crate::locale_of(ctx);
let confirm_label = self
.confirm_label
.unwrap_or_else(|| tr(locale, Key::ConfirmDefault));
let cancel_label = self
.cancel_label
.unwrap_or_else(|| tr(locale, Key::CancelDefault));
let mut outcome: Option<bool> = None;
let close = Dialog::new(self.title).show(
ctx,
|ui, _ctrl| {
let palette = crate::palette_of(ui.ctx());
ui.label(
egui::RichText::new(self.body)
.text_style(egui::TextStyle::Body)
.color(palette.text_secondary),
);
},
|ui, ctrl| {
let confirm = if self.danger {
Button::danger(confirm_label)
} else {
Button::primary(confirm_label)
};
if ui.add(confirm.size(ButtonSize::Md)).clicked() {
outcome = Some(true);
ctrl.close();
}
ui.add_space(SPACING.s2);
if ui.add(Button::secondary(cancel_label)).clicked() {
outcome = Some(false);
ctrl.close();
}
},
);
if outcome.is_none() && close {
outcome = Some(false);
}
outcome
}
}