servoshell: Port alert/confirm dialog code to use egui intead of tinyfiledialogs (#35399)

Signed-off-by: L Ashwin B <lashwinib@gmail.com>
This commit is contained in:
chickenleaf 2025-02-12 17:17:36 +05:30 committed by GitHub
parent 87069e9f47
commit 82df628a11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 140 additions and 91 deletions

View file

@ -21,7 +21,7 @@ use servo::{
GamepadHapticEffectType, LoadStatus, PermissionRequest, PromptDefinition, PromptOrigin, GamepadHapticEffectType, LoadStatus, PermissionRequest, PromptDefinition, PromptOrigin,
PromptResult, Servo, ServoDelegate, ServoError, TouchEventType, WebView, WebViewDelegate, PromptResult, Servo, ServoDelegate, ServoError, TouchEventType, WebView, WebViewDelegate,
}; };
use tinyfiledialogs::{self, MessageBoxIcon, OkCancel}; use tinyfiledialogs::{self, MessageBoxIcon};
use url::Url; use url::Url;
use super::app::{Present, PumpResult}; 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. // 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 { let present = if need_present {
Present::Deferred 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 { let Some(webview) = self.focused_webview() else {
return false; return false;
}; };
@ -238,7 +249,7 @@ impl RunningAppState {
let Some(dialogs) = inner.dialogs.get(&webview.id()) else { let Some(dialogs) = inner.dialogs.get(&webview.id()) else {
return false; return false;
}; };
dialogs.iter().any(Dialog::is_file_dialog) !dialogs.is_empty()
} }
pub(crate) fn get_focused_webview_index(&self) -> Option<usize> { pub(crate) fn get_focused_webview_index(&self) -> Option<usize> {
@ -347,44 +358,29 @@ impl WebViewDelegate for RunningAppState {
definition: PromptDefinition, definition: PromptDefinition,
origin: PromptOrigin, origin: PromptOrigin,
) { ) {
let res = if self.inner().headless { if self.inner().headless {
match definition { let _ = match definition {
PromptDefinition::Alert(_message, sender) => sender.send(()), PromptDefinition::Alert(_message, sender) => sender.send(()),
PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary), PromptDefinition::OkCancel(_message, sender) => sender.send(PromptResult::Primary),
PromptDefinition::Input(_message, default, sender) => { PromptDefinition::Input(_message, default, sender) => {
sender.send(Some(default.to_owned())) sender.send(Some(default.to_owned()))
}, },
};
return;
} }
} else { match definition {
thread::Builder::new() 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()) .name("AlertDialog".to_owned())
.spawn(move || match definition { .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) => { PromptDefinition::Input(mut message, mut default, sender) => {
if origin == PromptOrigin::Untrusted { if origin == PromptOrigin::Untrusted {
message = tiny_dialog_escape(&message); message = tiny_dialog_escape(&message);
@ -393,14 +389,13 @@ impl WebViewDelegate for RunningAppState {
let result = tinyfiledialogs::input_box("", &message, &default); let result = tinyfiledialogs::input_box("", &message, &default);
sender.send(result) sender.send(result)
}, },
_ => Ok(()),
}) })
.unwrap() .unwrap()
.join() .join()
.expect("Thread spawning failed") .expect("Thread spawning failed");
}; },
if let Err(e) = res {
webview.send_error(format!("Failed to send Prompt response: {e}"))
} }
} }
@ -502,18 +497,9 @@ impl WebViewDelegate for RunningAppState {
allow_select_mutiple: bool, allow_select_mutiple: bool,
response_sender: IpcSender<Option<Vec<PathBuf>>>, response_sender: IpcSender<Option<Vec<PathBuf>>>,
) { ) {
let mut inner_mut = self.inner_mut(); let file_dialog =
inner_mut Dialog::new_file_dialog(allow_select_mutiple, response_sender, filter_pattern);
.dialogs self.add_dialog(webview, file_dialog);
.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;
} }
fn request_permission(&self, _webview: servo::WebView, request: PermissionRequest) { fn request_permission(&self, _webview: servo::WebView, request: PermissionRequest) {

View file

@ -5,21 +5,26 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use egui::Modal;
use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog}; use egui_file_dialog::{DialogState, FileDialog as EguiFileDialog};
use log::warn; use log::warn;
use servo::ipc_channel::ipc::IpcSender; use servo::ipc_channel::ipc::IpcSender;
use servo::FilterPattern; use servo::{FilterPattern, PromptResult};
#[derive(Debug)] pub enum Dialog {
pub struct FileDialog { File {
dialog: EguiFileDialog, dialog: EguiFileDialog,
multiple: bool, multiple: bool,
response_sender: IpcSender<Option<Vec<PathBuf>>>, response_sender: IpcSender<Option<Vec<PathBuf>>>,
} },
Alert {
#[derive(Debug)] message: String,
pub enum Dialog { sender: IpcSender<()>,
File(FileDialog), },
OkCancel {
message: String,
sender: IpcSender<PromptResult>,
},
} }
impl Dialog { impl Dialog {
@ -45,44 +50,53 @@ impl Dialog {
.default_file_filter("All Supported Types"); .default_file_filter("All Supported Types");
} }
let dialog = FileDialog { Dialog::File {
dialog, dialog,
multiple, multiple,
response_sender, 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<PromptResult>) -> Self {
Dialog::OkCancel { message, sender }
} }
pub fn update(&mut self, ctx: &egui::Context) -> bool { pub fn update(&mut self, ctx: &egui::Context) -> bool {
match self { match self {
Dialog::File(dialog) => { Dialog::File {
if dialog.dialog.state() == DialogState::Closed { dialog,
if dialog.multiple { multiple,
dialog.dialog.pick_multiple(); response_sender,
} => {
if dialog.state() == DialogState::Closed {
if *multiple {
dialog.pick_multiple();
} else { } else {
dialog.dialog.pick_file(); dialog.pick_file();
} }
} }
let state = dialog.dialog.update(ctx).state(); let state = dialog.update(ctx).state();
match state { match state {
DialogState::Open => true, DialogState::Open => true,
DialogState::Picked(path) => { 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); warn!("Failed to send file selection response: {}", e);
} }
false false
}, },
DialogState::PickedMultiple(paths) => { 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); warn!("Failed to send file selection response: {}", e);
} }
false false
}, },
DialogState::Cancelled => { 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); warn!("Failed to send cancellation response: {}", e);
} }
false false
@ -90,10 +104,59 @@ impl Dialog {
DialogState::Closed => false, 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 { fn make_dialog_label(message: &str, ui: &mut egui::Ui) {
matches!(self, Dialog::File(..)) 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);
} }