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",
|
"Wdk_System_SystemServices",
|
||||||
"Win32_Graphics_Gdi",
|
"Win32_Graphics_Gdi",
|
||||||
"Win32_Graphics_DirectComposition",
|
"Win32_Graphics_DirectComposition",
|
||||||
|
"Win32_UI_Controls",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
|
"Win32_System_Com",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
"Win32_System_Time",
|
"Win32_System_Time",
|
||||||
|
@ -109,9 +109,13 @@ copypasta = "0.10.1"
|
|||||||
open = "5.0.1"
|
open = "5.0.1"
|
||||||
ashpd = "0.7.0"
|
ashpd = "0.7.0"
|
||||||
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] }
|
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-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"] }
|
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||||
as-raw-xcb-connection = "1"
|
as-raw-xcb-connection = "1"
|
||||||
|
@ -5,6 +5,7 @@ use std::{
|
|||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
ffi::{c_uint, c_void},
|
ffi::{c_uint, c_void},
|
||||||
|
os::windows::ffi::OsStrExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@ -14,7 +15,8 @@ use std::{
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||||
use futures::channel::oneshot::Receiver;
|
use futures::channel::oneshot::{self, Receiver};
|
||||||
|
use itertools::Itertools;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
use util::{ResultExt, SemanticVersion};
|
use util::{ResultExt, SemanticVersion};
|
||||||
@ -25,15 +27,17 @@ use windows::{
|
|||||||
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
|
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
|
||||||
Graphics::DirectComposition::DCompositionWaitForCompositorClock,
|
Graphics::DirectComposition::DCompositionWaitForCompositorClock,
|
||||||
System::{
|
System::{
|
||||||
|
Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL},
|
||||||
|
Ole::{OleInitialize, OleUninitialize},
|
||||||
|
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
|
||||||
Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
|
Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
|
||||||
{
|
|
||||||
Ole::{OleInitialize, OleUninitialize},
|
|
||||||
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
UI::{
|
UI::{
|
||||||
Input::KeyboardAndMouse::GetDoubleClickTime,
|
Input::KeyboardAndMouse::GetDoubleClickTime,
|
||||||
Shell::ShellExecuteW,
|
Shell::{
|
||||||
|
FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName,
|
||||||
|
ShellExecuteW, SIGDN_FILESYSPATH,
|
||||||
|
},
|
||||||
WindowsAndMessaging::{
|
WindowsAndMessaging::{
|
||||||
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
|
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
|
||||||
SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS,
|
SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS,
|
||||||
@ -342,9 +346,32 @@ impl Platform for WindowsPlatform {
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
|
||||||
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
|
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) {
|
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,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
|
iter::once,
|
||||||
num::NonZeroIsize,
|
num::NonZeroIsize,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
@ -14,7 +15,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use blade_graphics as gpu;
|
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 raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use windows::{
|
use windows::{
|
||||||
@ -33,6 +35,10 @@ use windows::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
UI::{
|
UI::{
|
||||||
|
Controls::{
|
||||||
|
TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON,
|
||||||
|
TD_INFORMATION_ICON, TD_WARNING_ICON,
|
||||||
|
},
|
||||||
Input::KeyboardAndMouse::{
|
Input::KeyboardAndMouse::{
|
||||||
GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1,
|
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,
|
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()
|
self.inner.input_handler.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&self,
|
&self,
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
@ -786,7 +791,72 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
detail: Option<&str>,
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[&str],
|
||||||
) -> Option<Receiver<usize>> {
|
) -> 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)
|
// todo(windows)
|
||||||
|
@ -5,4 +5,12 @@
|
|||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
</asmv3:windowsSettings>
|
</asmv3:windowsSettings>
|
||||||
</asmv3:application>
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type='win32'
|
||||||
|
name='Microsoft.Windows.Common-Controls'
|
||||||
|
version='6.0.0.0' processorArchitecture='*'
|
||||||
|
publicKeyToken='6595b64144ccf1df' />
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
</assembly>
|
</assembly>
|
||||||
|
Loading…
Reference in New Issue
Block a user