mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Impl prompts
and savefile dialog
on Windows (#9009)
### Description This is a part of #8809 , and this PR dose not include `open file dialog`, as I already saw two PRs impl this. https://github.com/zed-industries/zed/assets/14981363/3223490a-de77-4892-986f-97cf85aec3ae Release Notes: - N/A
This commit is contained in:
parent
bf295eac90
commit
a550b9cecf
@ -329,9 +329,11 @@ features = [
|
||||
"Wdk_System_SystemServices",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_DirectComposition",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Time",
|
||||
|
@ -109,9 +109,13 @@ copypasta = "0.10.1"
|
||||
open = "5.0.1"
|
||||
ashpd = "0.7.0"
|
||||
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] }
|
||||
wayland-client= { version = "0.31.2" }
|
||||
wayland-client = { version = "0.31.2" }
|
||||
wayland-cursor = "0.31.1"
|
||||
wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] }
|
||||
wayland-protocols = { version = "0.31.2", features = [
|
||||
"client",
|
||||
"staging",
|
||||
"unstable",
|
||||
] }
|
||||
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||
as-raw-xcb-connection = "1"
|
||||
|
@ -5,6 +5,7 @@ use std::{
|
||||
cell::RefCell,
|
||||
collections::HashSet,
|
||||
ffi::{c_uint, c_void},
|
||||
os::windows::ffi::OsStrExt,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
@ -14,7 +15,8 @@ use std::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_task::Runnable;
|
||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::Mutex;
|
||||
use time::UtcOffset;
|
||||
use util::{ResultExt, SemanticVersion};
|
||||
@ -25,15 +27,17 @@ use windows::{
|
||||
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
|
||||
Graphics::DirectComposition::DCompositionWaitForCompositorClock,
|
||||
System::{
|
||||
Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL},
|
||||
Ole::{OleInitialize, OleUninitialize},
|
||||
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
|
||||
Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
|
||||
{
|
||||
Ole::{OleInitialize, OleUninitialize},
|
||||
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
|
||||
},
|
||||
},
|
||||
UI::{
|
||||
Input::KeyboardAndMouse::GetDoubleClickTime,
|
||||
Shell::ShellExecuteW,
|
||||
Shell::{
|
||||
FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName,
|
||||
ShellExecuteW, SIGDN_FILESYSPATH,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
|
||||
SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS,
|
||||
@ -342,9 +346,32 @@ impl Platform for WindowsPlatform {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// todo(windows)
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
|
||||
unimplemented!()
|
||||
let directory = directory.to_owned();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.foreground_executor()
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let Ok(dialog) = show_savefile_dialog(directory) else {
|
||||
let _ = tx.send(None);
|
||||
return;
|
||||
};
|
||||
let Ok(_) = dialog.Show(None) else {
|
||||
let _ = tx.send(None); // user cancel
|
||||
return;
|
||||
};
|
||||
if let Ok(shell_item) = dialog.GetResult() {
|
||||
if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
|
||||
let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
fn reveal_path(&self, path: &Path) {
|
||||
@ -555,3 +582,26 @@ fn open_target(target: &str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
|
||||
let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
|
||||
let bind_context = CreateBindCtx(0)?;
|
||||
let Ok(full_path) = directory.canonicalize() else {
|
||||
return Ok(dialog);
|
||||
};
|
||||
let dir_str = full_path.into_os_string();
|
||||
if dir_str.is_empty() {
|
||||
return Ok(dialog);
|
||||
}
|
||||
let dir_vec = dir_str.encode_wide().collect_vec();
|
||||
let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
|
||||
.inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
|
||||
if ret.is_ok() {
|
||||
let dir_shell_item: IShellItem = ret.unwrap();
|
||||
let _ = dialog
|
||||
.SetFolder(&dir_shell_item)
|
||||
.inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
|
||||
}
|
||||
|
||||
Ok(dialog)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
ffi::c_void,
|
||||
iter::once,
|
||||
num::NonZeroIsize,
|
||||
path::PathBuf,
|
||||
rc::{Rc, Weak},
|
||||
@ -14,7 +15,8 @@ use std::{
|
||||
};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use smallvec::SmallVec;
|
||||
use windows::{
|
||||
@ -33,6 +35,10 @@ use windows::{
|
||||
},
|
||||
},
|
||||
UI::{
|
||||
Controls::{
|
||||
TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON,
|
||||
TD_INFORMATION_ICON, TD_WARNING_ICON,
|
||||
},
|
||||
Input::KeyboardAndMouse::{
|
||||
GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1,
|
||||
VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR,
|
||||
@ -778,7 +784,6 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.inner.input_handler.take()
|
||||
}
|
||||
|
||||
// todo(windows)
|
||||
fn prompt(
|
||||
&self,
|
||||
level: PromptLevel,
|
||||
@ -786,7 +791,72 @@ impl PlatformWindow for WindowsWindow {
|
||||
detail: Option<&str>,
|
||||
answers: &[&str],
|
||||
) -> Option<Receiver<usize>> {
|
||||
unimplemented!()
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
let msg = msg.to_string();
|
||||
let detail_string = match detail {
|
||||
Some(info) => Some(info.to_string()),
|
||||
None => None,
|
||||
};
|
||||
let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
let handle = self.inner.hwnd;
|
||||
self.inner
|
||||
.platform_inner
|
||||
.foreground_executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let mut config;
|
||||
config = std::mem::zeroed::<TASKDIALOGCONFIG>();
|
||||
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
|
||||
config.hwndParent = handle;
|
||||
let title;
|
||||
let main_icon;
|
||||
match level {
|
||||
crate::PromptLevel::Info => {
|
||||
title = windows::core::w!("Info");
|
||||
main_icon = TD_INFORMATION_ICON;
|
||||
}
|
||||
crate::PromptLevel::Warning => {
|
||||
title = windows::core::w!("Warning");
|
||||
main_icon = TD_WARNING_ICON;
|
||||
}
|
||||
crate::PromptLevel::Critical => {
|
||||
title = windows::core::w!("Critical");
|
||||
main_icon = TD_ERROR_ICON;
|
||||
}
|
||||
};
|
||||
config.pszWindowTitle = title;
|
||||
config.Anonymous1.pszMainIcon = main_icon;
|
||||
let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
|
||||
config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
|
||||
let hints_encoded;
|
||||
if let Some(ref hints) = detail_string {
|
||||
hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
|
||||
config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
|
||||
};
|
||||
let mut buttons = Vec::new();
|
||||
let mut btn_encoded = Vec::new();
|
||||
for (index, btn_string) in answers.iter().enumerate() {
|
||||
let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
|
||||
buttons.push(TASKDIALOG_BUTTON {
|
||||
nButtonID: index as _,
|
||||
pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
|
||||
});
|
||||
btn_encoded.push(encoded);
|
||||
}
|
||||
config.cButtons = buttons.len() as _;
|
||||
config.pButtons = buttons.as_ptr();
|
||||
|
||||
config.pfCallback = None;
|
||||
let mut res = std::mem::zeroed();
|
||||
let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
|
||||
.inspect_err(|e| log::error!("unable to create task dialog: {}", e));
|
||||
|
||||
let _ = done_tx.send(res as usize);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Some(done_rx)
|
||||
}
|
||||
|
||||
// todo(windows)
|
||||
|
@ -5,4 +5,12 @@
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type='win32'
|
||||
name='Microsoft.Windows.Common-Controls'
|
||||
version='6.0.0.0' processorArchitecture='*'
|
||||
publicKeyToken='6595b64144ccf1df' />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
|
Loading…
Reference in New Issue
Block a user