windows: Dialog QoL improvements (#33241)

Just like in the previous PR #33230, we need to properly set up modal
windows to make them work as expected.

Before this PR, when you opened an "Open File" or "Save File" dialog,
clicking the main window would steal focus from the modal, even though
the main window wasn’t actually interactive.

With this PR, clicking the main window while a modal is open does
nothing — as it should — until the modal is closed.

#### Before



https://github.com/user-attachments/assets/9c6bdff0-1c46-49c1-a5ff-751c52c7d613

#### After



https://github.com/user-attachments/assets/8776bd28-85ff-4f32-8390-bcf5b4eec1fe





Release Notes:

- N/A
This commit is contained in:
张小白 2025-06-23 19:02:00 +08:00 committed by GitHub
parent e68b95c61b
commit 272fc672af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -295,6 +295,18 @@ impl WindowsPlatform {
.log_err() .log_err()
.unwrap_or_default() .unwrap_or_default()
} }
fn find_current_active_window(&self) -> Option<HWND> {
let active_window_hwnd = unsafe { GetActiveWindow() };
if active_window_hwnd.is_invalid() {
return None;
}
self.raw_window_handles
.read()
.iter()
.find(|&&hwnd| hwnd == active_window_hwnd)
.copied()
}
} }
impl Platform for WindowsPlatform { impl Platform for WindowsPlatform {
@ -473,9 +485,10 @@ impl Platform for WindowsPlatform {
options: PathPromptOptions, options: PathPromptOptions,
) -> Receiver<Result<Option<Vec<PathBuf>>>> { ) -> Receiver<Result<Option<Vec<PathBuf>>>> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let window = self.find_current_active_window();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
let _ = tx.send(file_open_dialog(options)); let _ = tx.send(file_open_dialog(options, window));
}) })
.detach(); .detach();
@ -485,9 +498,10 @@ impl Platform for WindowsPlatform {
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> { fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned(); let directory = directory.to_owned();
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let window = self.find_current_active_window();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
let _ = tx.send(file_save_dialog(directory)); let _ = tx.send(file_save_dialog(directory, window));
}) })
.detach(); .detach();
@ -754,7 +768,10 @@ fn open_target_in_explorer(target: &str) {
} }
} }
fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>> { fn file_open_dialog(
options: PathPromptOptions,
window: Option<HWND>,
) -> Result<Option<Vec<PathBuf>>> {
let folder_dialog: IFileOpenDialog = let folder_dialog: IFileOpenDialog =
unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? }; unsafe { CoCreateInstance(&FileOpenDialog, None, CLSCTX_ALL)? };
@ -768,7 +785,7 @@ fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>>
unsafe { unsafe {
folder_dialog.SetOptions(dialog_options)?; folder_dialog.SetOptions(dialog_options)?;
if folder_dialog.Show(None).is_err() { if folder_dialog.Show(window).is_err() {
// User cancelled // User cancelled
return Ok(None); return Ok(None);
} }
@ -790,7 +807,7 @@ fn file_open_dialog(options: PathPromptOptions) -> Result<Option<Vec<PathBuf>>>
Ok(Some(paths)) Ok(Some(paths))
} }
fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> { fn file_save_dialog(directory: PathBuf, window: Option<HWND>) -> Result<Option<PathBuf>> {
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? }; let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
if !directory.to_string_lossy().is_empty() { if !directory.to_string_lossy().is_empty() {
if let Some(full_path) = directory.canonicalize().log_err() { if let Some(full_path) = directory.canonicalize().log_err() {
@ -806,7 +823,7 @@ fn file_save_dialog(directory: PathBuf) -> Result<Option<PathBuf>> {
pszName: windows::core::w!("All files"), pszName: windows::core::w!("All files"),
pszSpec: windows::core::w!("*.*"), pszSpec: windows::core::w!("*.*"),
}])?; }])?;
if dialog.Show(None).is_err() { if dialog.Show(window).is_err() {
// User cancelled // User cancelled
return Ok(None); return Ok(None);
} }