use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use crate::error::{FerriError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DialogType {
Alert,
BeforeUnload,
Confirm,
Prompt,
}
impl DialogType {
#[must_use]
pub fn parse(s: &str) -> Self {
match s {
"beforeunload" => Self::BeforeUnload,
"confirm" => Self::Confirm,
"prompt" => Self::Prompt,
_ => Self::Alert,
}
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Alert => "alert",
Self::BeforeUnload => "beforeunload",
Self::Confirm => "confirm",
Self::Prompt => "prompt",
}
}
}
pub enum DialogResponse {
Accept { prompt_text: Option<String> },
Dismiss,
}
pub type DialogResponder = Arc<
dyn Fn(DialogResponse) -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::error::Result<()>> + Send>>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct Dialog {
pub(crate) inner: Arc<DialogState>,
}
pub(crate) struct DialogState {
dialog_type: DialogType,
message: String,
default_value: String,
handled: AtomicBool,
responder: DialogResponder,
manager: Option<DialogManager>,
}
impl Dialog {
#[must_use]
pub fn new(dialog_type: DialogType, message: String, default_value: String, responder: DialogResponder) -> Self {
Self::new_with_manager(dialog_type, message, default_value, responder, None)
}
#[must_use]
pub fn new_with_manager(
dialog_type: DialogType,
message: String,
default_value: String,
responder: DialogResponder,
manager: Option<DialogManager>,
) -> Self {
Self {
inner: Arc::new(DialogState {
dialog_type,
message,
default_value,
handled: AtomicBool::new(false),
responder,
manager,
}),
}
}
#[must_use]
pub fn dialog_type(&self) -> DialogType {
self.inner.dialog_type
}
#[must_use]
pub fn message(&self) -> &str {
&self.inner.message
}
#[must_use]
pub fn default_value(&self) -> &str {
&self.inner.default_value
}
#[must_use]
pub fn is_handled(&self) -> bool {
self.inner.handled.load(Ordering::Acquire)
}
pub async fn accept(&self, prompt_text: Option<String>) -> Result<()> {
self.mark_handled_or_error()?;
if let Some(mgr) = &self.inner.manager {
mgr.dialog_will_close(self);
}
(self.inner.responder)(DialogResponse::Accept { prompt_text }).await
}
pub async fn dismiss(&self) -> Result<()> {
self.mark_handled_or_error()?;
if let Some(mgr) = &self.inner.manager {
mgr.dialog_will_close(self);
}
(self.inner.responder)(DialogResponse::Dismiss).await
}
pub(crate) async fn auto_close(&self) {
if self.inner.handled.swap(true, Ordering::AcqRel) {
return;
}
let response = if matches!(self.inner.dialog_type, DialogType::BeforeUnload) {
DialogResponse::Accept { prompt_text: None }
} else {
DialogResponse::Dismiss
};
let _ = (self.inner.responder)(response).await;
}
fn mark_handled_or_error(&self) -> Result<()> {
if self.inner.handled.swap(true, Ordering::AcqRel) {
return Err(FerriError::Backend(
"Cannot accept dialog which is already handled!".into(),
));
}
Ok(())
}
}
impl std::fmt::Debug for Dialog {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dialog")
.field("type", &self.inner.dialog_type.as_str())
.field("message", &self.inner.message)
.field("default_value", &self.inner.default_value)
.field("handled", &self.is_handled())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DialogHandlerId(pub u64);
pub type DialogHandlerFn = Arc<dyn Fn(&Dialog) -> bool + Send + Sync>;
struct DialogHandlerEntry {
id: u64,
handler: DialogHandlerFn,
}
#[derive(Clone, Default)]
pub struct DialogManager {
inner: Arc<DialogManagerState>,
}
#[derive(Default)]
struct DialogManagerState {
handlers: std::sync::Mutex<Vec<DialogHandlerEntry>>,
next_id: AtomicU64,
open: std::sync::Mutex<Vec<Dialog>>,
}
impl DialogManager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_handler(&self, handler: DialogHandlerFn) -> DialogHandlerId {
let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
if let Ok(mut handlers) = self.inner.handlers.lock() {
handlers.push(DialogHandlerEntry { id, handler });
}
DialogHandlerId(id)
}
pub fn remove_handler(&self, id: DialogHandlerId) {
let becomes_empty = if let Ok(mut handlers) = self.inner.handlers.lock() {
handlers.retain(|h| h.id != id.0);
handlers.is_empty()
} else {
false
};
if becomes_empty {
let drained: Vec<Dialog> = match self.inner.open.lock() {
Ok(mut g) => std::mem::take(&mut *g),
Err(_) => Vec::new(),
};
for dialog in drained {
tokio::spawn(async move {
dialog.auto_close().await;
});
}
}
}
pub fn did_open(&self, dialog: Dialog) {
let handlers: Vec<DialogHandlerFn> = match self.inner.handlers.lock() {
Ok(g) => g.iter().map(|e| Arc::clone(&e.handler)).collect(),
Err(_) => Vec::new(),
};
let mut claimed = false;
for h in handlers {
if h(&dialog) {
claimed = true;
}
}
if claimed {
if let Ok(mut open) = self.inner.open.lock() {
open.push(dialog);
}
} else {
tokio::spawn(async move {
dialog.auto_close().await;
});
}
}
pub fn dialog_will_close(&self, dialog: &Dialog) {
if let Ok(mut open) = self.inner.open.lock() {
open.retain(|d| !Arc::ptr_eq(&d.inner, &dialog.inner));
}
}
#[must_use]
pub fn open_dialog_count(&self) -> usize {
self.inner.open.lock().map_or(0, |g| g.len())
}
#[must_use]
pub fn register_emitter_bridge(&self, events: crate::events::EventEmitter) -> DialogHandlerId {
self.add_handler(Arc::new(move |dialog: &Dialog| {
if events.has_listener("dialog") {
events.emit(crate::events::PageEvent::Dialog(dialog.clone()));
true
} else {
false
}
}))
}
}