use std::{any::Any, collections::VecDeque, sync::Arc};
use egui::{
Color32, CornerRadius, Id, LayerId, Margin, Order, Rect, Sense, Style, Ui, UiBuilder, Vec2,
WidgetText,
};
use crate::*;
pub struct DialogContext {
pub dialog_id: Option<Id>,
pub animation: Option<fn(f32) -> f32>,
pub opacity: f32,
pub already_closed: bool,
pub mask_rect: Rect,
pub min_size: Option<Vec2>,
pub max_size: Option<Vec2>,
}
pub struct DialogResponse {
pub id: Option<Id>,
pub reply: Option<Box<dyn Any>>,
}
impl DialogResponse {
#[inline]
pub fn is(&self, id: impl Into<Id>) -> bool {
self.id == Some(id.into())
}
#[inline]
pub fn is_reply(&self) -> bool {
self.reply.is_some()
}
#[inline]
pub fn is_reply_of(&self, id: impl Into<Id>) -> bool {
self.id == Some(id.into()) && self.reply.is_some()
}
#[inline]
pub fn reply<Reply: Any>(self) -> Result<Reply, Self> {
match self.reply {
Some(reply) => reply.downcast().map(|r| *r).map_err(|r| DialogResponse {
id: self.id,
reply: Some(r),
}),
None => Err(self),
}
}
#[inline]
pub fn reply_ref<Reply: Any>(&self) -> Option<&Reply> {
self.reply.as_ref().and_then(|r| r.downcast_ref())
}
#[inline]
pub fn reply_mut<Reply: Any>(&mut self) -> Option<&mut Reply> {
self.reply.as_mut().and_then(|r| r.downcast_mut())
}
}
pub trait AbstractDialog {
fn update(&mut self, ctx: &egui::Context, dctx: &DialogContext) -> Option<Box<dyn Any>>;
fn mask(&self) -> Option<Color32>;
fn id(&self) -> Option<Id>;
}
impl<'a, R> AbstractDialog for DialogDetails<'a, R>
where
R: Any,
{
fn update(&mut self, ctx: &egui::Context, dctx: &DialogContext) -> Option<Box<dyn Any>> {
self.dialog
.show(ctx, dctx)
.map(|r| Box::new(r) as Box<dyn Any>)
}
fn mask(&self) -> Option<Color32> {
self.mask
}
fn id(&self) -> Option<Id> {
self.id
}
}
pub struct Dialogs<'a> {
dialogs: VecDeque<Box<dyn AbstractDialog + 'a>>,
pub mask_margin: Margin,
pub mask_rounding: CornerRadius,
pub animation: Option<fn(f32) -> f32>,
fading_dialog: Option<Box<dyn AbstractDialog + 'a>>,
pub style: Option<Arc<Style>>,
pub min_size: Option<Vec2>,
pub max_size: Option<Vec2>,
}
impl Dialogs<'_> {
#[inline]
pub fn new() -> Self {
Self {
dialogs: VecDeque::new(),
mask_margin: Margin::ZERO,
mask_rounding: CornerRadius::ZERO,
animation: Some(egui::emath::easing::cubic_out),
fading_dialog: None,
style: None,
min_size: None,
max_size: None,
}
}
#[inline]
pub fn mask_margin(mut self, margin: impl Into<Margin>) -> Self {
self.mask_margin = margin.into();
self
}
#[inline]
pub fn mask_rounding(mut self, rounding: impl Into<CornerRadius>) -> Self {
self.mask_rounding = rounding.into();
self
}
pub fn animate(mut self, animation: Option<fn(f32) -> f32>) -> Self {
self.animation = animation;
if animation.is_none() && self.fading_dialog.is_some() {
self.fading_dialog = None;
}
self
}
pub fn animated(mut self, is_animated: bool) -> Self {
if is_animated {
if self.animation.is_none() {
self.animation = Some(egui::emath::easing::cubic_out);
}
} else {
self.animation = None;
self.fading_dialog = None;
}
self
}
#[inline]
pub fn style(mut self, style: impl Into<Arc<Style>>) -> Self {
self.style = Some(style.into());
self
}
#[inline]
pub fn min_size(mut self, size: impl Into<Vec2>) -> Self {
self.min_size = Some(size.into());
self
}
#[inline]
pub fn max_size(mut self, size: impl Into<Vec2>) -> Self {
self.max_size = Some(size.into());
self
}
}
impl Default for Dialogs<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'a> Dialogs<'a> {
#[inline]
pub fn add<Reply: 'a + Any>(&mut self, dialog: DialogDetails<'a, Reply>) {
self.dialogs.push_back(Box::new(dialog));
}
#[inline]
pub fn add_immediate<Reply: 'a + Any>(&mut self, dialog: DialogDetails<'a, Reply>) {
self.dialogs.push_front(Box::new(dialog));
}
#[inline]
pub fn add_if_absent<Reply: 'a + Any>(&mut self, dialog: DialogDetails<'a, Reply>) {
if dialog.id.map_or(true, |id| !self.is_open(id)) {
self.add(dialog);
}
}
#[inline]
pub fn current_dialog(&self) -> Option<&Box<dyn AbstractDialog + 'a>> {
self.dialogs.front()
}
#[inline]
pub fn is_open(&self, id: impl Into<Id>) -> bool {
let id = id.into();
self.dialogs.iter().any(|dialog| dialog.id() == Some(id))
}
#[inline]
pub fn pop_front(&mut self) -> Option<Box<dyn AbstractDialog + 'a>> {
self.dialogs.pop_front()
}
#[inline]
pub fn last_dialog(&self) -> Option<&Box<dyn AbstractDialog + 'a>> {
self.dialogs.back()
}
#[inline]
pub fn pop_back(&mut self) -> Option<Box<dyn AbstractDialog + 'a>> {
self.dialogs.pop_back()
}
#[inline]
pub fn count(&self) -> usize {
self.dialogs.len()
}
#[inline]
pub fn dialogs(&self) -> &VecDeque<Box<dyn AbstractDialog + 'a>> {
&self.dialogs
}
#[inline]
pub fn dialogs_mut(&mut self) -> &mut VecDeque<Box<dyn AbstractDialog + 'a>> {
&mut self.dialogs
}
}
impl Dialogs<'_> {
const ID_NAME: &'static str = "dialog_mask";
pub fn show_mask(&self, ctx: &egui::Context, color: Color32, dialog_on: bool) -> f32 {
let id = Id::new((ctx.viewport_id(), Self::ID_NAME));
let how_on = match self.animation {
Some(easing) => {
let value = ctx.animate_bool_with_easing(id, dialog_on, easing);
if value == 0. {
return 0.;
}
value
}
None => {
if dialog_on {
1.
} else {
return 0.;
}
}
};
let layer_id = LayerId {
order: egui::Order::Background,
id,
};
let mask_rect = ctx.content_rect() - self.mask_margin;
let mut mask_ui = Ui::new(
ctx.clone(),
id,
UiBuilder::new().layer_id(layer_id).max_rect(mask_rect),
);
mask_ui.set_opacity(how_on);
mask_ui
.painter()
.rect_filled(mask_rect, self.mask_rounding, color);
mask_ui.allocate_rect(mask_rect, Sense::hover());
let focused = ctx
.memory(|r| r.focused())
.and_then(|id| ctx.read_response(id));
if let Some(focused) = focused {
if focused.layer_id.order == Order::Background {
focused.surrender_focus();
}
}
how_on
}
pub fn show(&mut self, ctx: &egui::Context) -> Option<DialogResponse> {
let on = !self.dialogs.is_empty() && self.fading_dialog.is_none();
let how_on = if on || self.fading_dialog.is_some() {
let mask_color = match &self.fading_dialog {
Some(fading_dialog) => fading_dialog.mask(),
None => self.dialogs.front().unwrap().mask(), };
if let Some(mask_color) = mask_color {
self.show_mask(ctx, mask_color, on)
} else if let Some(animation) = self.animation {
ctx.animate_bool_with_easing(
Id::new((ctx.viewport_id(), Self::ID_NAME)),
on,
animation,
)
} else {
1.
}
} else {
0.
};
if how_on == 0. {
if self.fading_dialog.is_some() {
self.fading_dialog = None;
if !self.dialogs.is_empty() {
ctx.request_repaint();
}
}
return None;
}
let (dialog, already_closed) = match self.fading_dialog {
Some(ref mut fading_dialog) => (Some(fading_dialog), true),
None => (self.dialogs.front_mut(), false),
};
if let Some(dialog) = dialog {
let id = dialog.id();
let mut response = DialogResponse { id, reply: None };
let outer_style = if let Some(ref style) = self.style {
let outer_style = ctx.style();
ctx.set_style(Arc::clone(style));
Some(outer_style)
} else {
None
};
let dctx = &DialogContext {
dialog_id: id,
animation: self.animation,
opacity: how_on,
already_closed,
mask_rect: ctx.content_rect() - self.mask_margin,
min_size: self.min_size,
max_size: self.max_size,
};
if let Some(reply) = dialog.update(ctx, dctx) {
if !already_closed {
response.reply = Some(reply);
let closed_dialog = self.dialogs.pop_front();
if self.animation.is_some() {
self.fading_dialog = closed_dialog;
}
}
}
if let Some(outer_style) = outer_style {
ctx.set_style(outer_style);
}
Some(response)
} else {
None
}
}
}
impl<'a> Dialogs<'a> {
#[inline]
pub fn info(&mut self, title: impl Into<WidgetText>, message: impl Into<WidgetText>) {
self.add(StandardDialogDetails::info(title, message));
}
#[inline]
pub fn success(&mut self, title: impl Into<WidgetText>, message: impl Into<WidgetText>) {
self.add(StandardDialogDetails::success(title, message));
}
#[inline]
pub fn confirm<R: Any>(
&mut self,
title: impl Into<WidgetText>,
message: impl Into<WidgetText>,
config: impl FnOnce(StandardDialogDetails) -> DialogDetails<R>,
) {
self.add(config(StandardDialogDetails::confirm(title, message)));
}
#[inline]
pub fn warning(&mut self, title: impl Into<WidgetText>, message: impl Into<WidgetText>) {
self.add(StandardDialogDetails::warning(title, message));
}
#[inline]
pub fn error(&mut self, title: impl Into<WidgetText>, message: impl Into<WidgetText>) {
self.add(StandardDialogDetails::error(title, message));
}
}