diff --git a/ports/servoshell/desktop/app_state.rs b/ports/servoshell/desktop/app_state.rs index 1fee84cd34d..d80e88aaedc 100644 --- a/ports/servoshell/desktop/app_state.rs +++ b/ports/servoshell/desktop/app_state.rs @@ -21,7 +21,7 @@ use servo::{ GamepadHapticEffectType, LoadStatus, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, Servo, ServoDelegate, ServoError, TouchEventType, WebView, WebViewDelegate, }; -use tinyfiledialogs::{self, MessageBoxIcon, OkCancel}; +use tinyfiledialogs::{self, MessageBoxIcon}; use url::Url; use super::app::{Present, PumpResult}; @@ -142,7 +142,7 @@ impl RunningAppState { } // Currently, egui-file-dialog dialogs need to be constantly presented or animations aren't fluid. - let need_present = need_present || self.has_active_file_dialog(); + let need_present = need_present || self.has_active_dialog(); let present = if need_present { Present::Deferred @@ -230,7 +230,18 @@ impl RunningAppState { } } - fn has_active_file_dialog(&self) -> bool { + fn add_dialog(&self, webview: servo::WebView, dialog: Dialog) { + let mut inner_mut = self.inner_mut(); + inner_mut + .dialogs + .entry(webview.id()) + .or_default() + .push(dialog); + inner_mut.need_update = true; + inner_mut.need_present = true; + } + + fn has_active_dialog(&self) -> bool { let Some(webview) = self.focused_webview() else { return false; }; @@ -238,7 +249,7 @@ impl RunningAppState { let Some(dialogs) = inner.dialogs.get(&webview.id()) else { return false; }; - dialogs.iter().any(Dialog::is_file_dialog) + !dialogs.is_empty() } pub(crate) fn get_focused_webview_index(&self) -> Option { @@ -347,60 +358,44 @@ impl WebViewDelegate for RunningAppState { definition: PromptDefinition, origin: PromptOrigin, ) { - let res = if self.inner().headless { - match definition { + if self.inner().headless { + let _ = match definition { PromptDefinition::Alert(_message, sender) => sender.send(()), PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary), PromptDefinition::Input(_message, default, sender) => { sender.send(Some(default.to_owned())) }, - } - } else { - thread::Builder::new() - .name("AlertDialog".to_owned()) - .spawn(move || match definition { - PromptDefinition::Alert(mut message, sender) => { - if origin == PromptOrigin::Untrusted { - message = tiny_dialog_escape(&message); - } - tinyfiledialogs::message_box_ok( - "Alert!", - &message, - MessageBoxIcon::Warning, - ); - sender.send(()) - }, - PromptDefinition::OkCancel(mut message, sender) => { - if origin == PromptOrigin::Untrusted { - message = tiny_dialog_escape(&message); - } - let result = tinyfiledialogs::message_box_ok_cancel( - "", - &message, - MessageBoxIcon::Warning, - OkCancel::Cancel, - ); - sender.send(match result { - OkCancel::Ok => PromptResult::Primary, - OkCancel::Cancel => PromptResult::Secondary, - }) - }, - PromptDefinition::Input(mut message, mut default, sender) => { - if origin == PromptOrigin::Untrusted { - message = tiny_dialog_escape(&message); - default = tiny_dialog_escape(&default); - } - let result = tinyfiledialogs::input_box("", &message, &default); - sender.send(result) - }, - }) - .unwrap() - .join() - .expect("Thread spawning failed") - }; + }; + return; + } + match definition { + PromptDefinition::Alert(message, sender) => { + let alert_dialog = Dialog::new_alert_dialog(message, sender); + self.add_dialog(webview, alert_dialog); + }, + PromptDefinition::OkCancel(message, sender) => { + let okcancel_dialog = Dialog::new_okcancel_dialog(message, sender); + self.add_dialog(webview, okcancel_dialog); + }, + _ => { + let _ = thread::Builder::new() + .name("AlertDialog".to_owned()) + .spawn(move || match definition { + PromptDefinition::Input(mut message, mut default, sender) => { + if origin == PromptOrigin::Untrusted { + message = tiny_dialog_escape(&message); + default = tiny_dialog_escape(&default); + } + let result = tinyfiledialogs::input_box("", &message, &default); + sender.send(result) + }, - if let Err(e) = res { - webview.send_error(format!("Failed to send Prompt response: {e}")) + _ => Ok(()), + }) + .unwrap() + .join() + .expect("Thread spawning failed"); + }, } } @@ -502,18 +497,9 @@ impl WebViewDelegate for RunningAppState { allow_select_mutiple: bool, response_sender: IpcSender>>, ) { - let mut inner_mut = self.inner_mut(); - inner_mut - .dialogs - .entry(webview.id()) - .or_default() - .push(Dialog::new_file_dialog( - allow_select_mutiple, - response_sender, - filter_pattern, - )); - inner_mut.need_update = true; - inner_mut.need_present = true; + let file_dialog = + Dialog::new_file_dialog(allow_select_mutiple, response_sender, filter_pattern); + self.add_dialog(webview, file_dialog); } fn request_permission(&self, _webview: servo::WebView, request: PermissionRequest) { diff --git a/ports/servoshell/desktop/dialog.rs b/ports/servoshell/desktop/dialog.rs index 68aa3ca5356..d184dee7648 100644 --- a/ports/servoshell/desktop/dialog.rs +++ b/ports/servoshell/desktop/dialog.rs @@ -5,21 +5,26 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; +use egui::Modal; use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog}; use log::warn; use servo::ipc_channel::ipc::IpcSender; -use servo::FilterPattern; +use servo::{FilterPattern, PromptResult}; -#[derive(Debug)] -pub struct FileDialog { - dialog: EguiFileDialog, - multiple: bool, - response_sender: IpcSender>>, -} - -#[derive(Debug)] pub enum Dialog { - File(FileDialog), + File { + dialog: EguiFileDialog, + multiple: bool, + response_sender: IpcSender>>, + }, + Alert { + message: String, + sender: IpcSender<()>, + }, + OkCancel { + message: String, + sender: IpcSender, + }, } impl Dialog { @@ -45,44 +50,53 @@ impl Dialog { .default_file_filter("All Supported Types"); } - let dialog = FileDialog { + Dialog::File { dialog, multiple, response_sender, - }; + } + } - Dialog::File(dialog) + pub fn new_alert_dialog(message: String, sender: IpcSender<()>) -> Self { + Dialog::Alert { message, sender } + } + + pub fn new_okcancel_dialog(message: String, sender: IpcSender) -> Self { + Dialog::OkCancel { message, sender } } pub fn update(&mut self, ctx: &egui::Context) -> bool { match self { - Dialog::File(dialog) => { - if dialog.dialog.state() == DialogState::Closed { - if dialog.multiple { - dialog.dialog.pick_multiple(); + Dialog::File { + dialog, + multiple, + response_sender, + } => { + if dialog.state() == DialogState::Closed { + if *multiple { + dialog.pick_multiple(); } else { - dialog.dialog.pick_file(); + dialog.pick_file(); } } - let state = dialog.dialog.update(ctx).state(); - + let state = dialog.update(ctx).state(); match state { DialogState::Open => true, DialogState::Picked(path) => { - if let Err(e) = dialog.response_sender.send(Some(vec![path])) { + if let Err(e) = response_sender.send(Some(vec![path])) { warn!("Failed to send file selection response: {}", e); } false }, DialogState::PickedMultiple(paths) => { - if let Err(e) = dialog.response_sender.send(Some(paths)) { + if let Err(e) = response_sender.send(Some(paths)) { warn!("Failed to send file selection response: {}", e); } false }, DialogState::Cancelled => { - if let Err(e) = dialog.response_sender.send(None) { + if let Err(e) = response_sender.send(None) { warn!("Failed to send cancellation response: {}", e); } false @@ -90,10 +104,59 @@ impl Dialog { DialogState::Closed => false, } }, + Dialog::Alert { message, sender } => { + let mut is_open = true; + let modal = Modal::new("alert".into()); + modal.show(ctx, |ui| { + make_dialog_label(message, ui); + egui::Sides::new().show( + ui, + |_ui| {}, + |ui| { + if ui.button("Close").clicked() { + is_open = false; + if let Err(e) = sender.send(()) { + warn!("Failed to send alert dialog response: {}", e); + } + } + }, + ); + }); + is_open + }, + Dialog::OkCancel { message, sender } => { + let mut is_open = true; + let modal = Modal::new("OkCancel".into()); + modal.show(ctx, |ui| { + make_dialog_label(message, ui); + egui::Sides::new().show( + ui, + |_ui| {}, + |ui| { + if ui.button("Ok").clicked() { + is_open = false; + if let Err(e) = sender.send(PromptResult::Primary) { + warn!("Failed to send alert dialog response: {}", e); + } + } + if ui.button("Cancel").clicked() { + is_open = false; + if let Err(e) = sender.send(PromptResult::Secondary) { + warn!("Failed to send alert dialog response: {}", e); + } + } + }, + ); + }); + is_open + }, } } - - pub(crate) fn is_file_dialog(&self) -> bool { - matches!(self, Dialog::File(..)) - } +} + +fn make_dialog_label(message: &str, ui: &mut egui::Ui) { + let mut frame = egui::Frame::default().inner_margin(10.0).begin(ui); + frame.content_ui.set_min_width(150.0); + frame.content_ui.label(message); + frame.end(ui); }