Windows gpui platform (#8490)

First implementation of gpui platform for Windows.

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
白山風露 2024-03-04 03:53:22 +09:00 committed by GitHub
parent a88df2c103
commit 69e0474ebb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1610 additions and 33 deletions

78
Cargo.lock generated
View file

@ -4249,6 +4249,7 @@ dependencies = [
"wayland-backend",
"wayland-client",
"wayland-protocols",
"windows 0.53.0",
"xcb",
"xkbcommon",
]
@ -11732,6 +11733,35 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538"
dependencies = [
"windows-core",
"windows-targets 0.52.3",
]
[[package]]
name = "windows-core"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd"
dependencies = [
"windows-result",
"windows-targets 0.52.3",
]
[[package]]
name = "windows-result"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64"
dependencies = [
"windows-targets 0.52.3",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@ -11756,7 +11786,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets 0.52.3",
]
[[package]]
@ -11791,17 +11821,17 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm 0.52.3",
"windows_aarch64_msvc 0.52.3",
"windows_i686_gnu 0.52.3",
"windows_i686_msvc 0.52.3",
"windows_x86_64_gnu 0.52.3",
"windows_x86_64_gnullvm 0.52.3",
"windows_x86_64_msvc 0.52.3",
]
[[package]]
@ -11818,9 +11848,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
[[package]]
name = "windows_aarch64_msvc"
@ -11836,9 +11866,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
[[package]]
name = "windows_i686_gnu"
@ -11854,9 +11884,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
[[package]]
name = "windows_i686_msvc"
@ -11872,9 +11902,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
[[package]]
name = "windows_x86_64_gnu"
@ -11890,9 +11920,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
[[package]]
name = "windows_x86_64_gnullvm"
@ -11908,9 +11938,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
[[package]]
name = "windows_x86_64_msvc"
@ -11926,9 +11956,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
[[package]]
name = "winnow"

View file

@ -92,8 +92,16 @@ media.workspace = true
metal = "0.25"
objc = "0.2"
[target.'cfg(target_os = "linux")'.dependencies]
[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies]
flume = "0.11"
#TODO: use these on all platforms
blade-graphics.workspace = true
blade-macros.workspace = true
blade-rwh.workspace = true
bytemuck = "1"
cosmic-text = "0.10.0"
[target.'cfg(target_os = "linux")'.dependencies]
open = "5.0.1"
ashpd = "0.7.0"
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] }
@ -104,12 +112,15 @@ xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
as-raw-xcb-connection = "1"
calloop = "0.12.4"
calloop-wayland-source = "0.2.0"
#TODO: use these on all platforms
blade-graphics.workspace = true
blade-macros.workspace = true
blade-rwh.workspace = true
bytemuck = "1"
cosmic-text = "0.10.0"
[target.'cfg(windows)'.dependencies.windows]
version = "0.53.0"
features = [
"Win32_Graphics_Gdi",
"Win32_UI_WindowsAndMessaging",
"Win32_Security",
"Win32_System_Threading",
]
[[example]]
name = "hello_world"

View file

@ -12,12 +12,15 @@ mod linux;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(any(target_os = "linux", feature = "macos-blade"))]
#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))]
mod blade;
#[cfg(any(test, feature = "test-support"))]
mod test;
#[cfg(target_os = "windows")]
mod windows;
use crate::{
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
@ -54,6 +57,8 @@ pub(crate) use mac::*;
pub(crate) use test::*;
use time::UtcOffset;
pub use util::SemanticVersion;
#[cfg(target_os = "windows")]
pub(crate) use windows::*;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
@ -66,7 +71,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
// todo("windows")
#[cfg(target_os = "windows")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
unimplemented!()
Rc::new(WindowsPlatform::new())
}
pub(crate) trait Platform: 'static {

View file

@ -0,0 +1,13 @@
mod dispatcher;
mod display;
mod platform;
mod text_system;
mod util;
mod window;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
pub(crate) use platform::*;
pub(crate) use text_system::*;
pub(crate) use util::*;
pub(crate) use window::*;

View file

@ -0,0 +1,159 @@
use std::{
cmp::Ordering,
thread::{current, JoinHandle, ThreadId},
time::{Duration, Instant},
};
use async_task::Runnable;
use collections::BinaryHeap;
use flume::{RecvTimeoutError, Sender};
use parking::Parker;
use parking_lot::Mutex;
use windows::Win32::{Foundation::HANDLE, System::Threading::SetEvent};
use crate::{PlatformDispatcher, TaskLabel};
pub(crate) struct WindowsDispatcher {
background_sender: Sender<(Runnable, Option<TaskLabel>)>,
main_sender: Sender<Runnable>,
timer_sender: Sender<(Runnable, Duration)>,
background_threads: Vec<JoinHandle<()>>,
timer_thread: JoinHandle<()>,
parker: Mutex<Parker>,
main_thread_id: ThreadId,
event: HANDLE,
}
impl WindowsDispatcher {
pub(crate) fn new(main_sender: Sender<Runnable>, event: HANDLE) -> Self {
let parker = Mutex::new(Parker::new());
let (background_sender, background_receiver) =
flume::unbounded::<(Runnable, Option<TaskLabel>)>();
let background_threads = (0..std::thread::available_parallelism()
.map(|i| i.get())
.unwrap_or(1))
.map(|_| {
let receiver = background_receiver.clone();
std::thread::spawn(move || {
for (runnable, label) in receiver {
if let Some(label) = label {
log::debug!("TaskLabel: {label:?}");
}
runnable.run();
}
})
})
.collect::<Vec<_>>();
let (timer_sender, timer_receiver) = flume::unbounded::<(Runnable, Duration)>();
let timer_thread = std::thread::spawn(move || {
let mut runnables = BinaryHeap::<RunnableAfter>::new();
let mut timeout_dur = None;
loop {
let recv = if let Some(dur) = timeout_dur {
match timer_receiver.recv_timeout(dur) {
Ok(recv) => Some(recv),
Err(RecvTimeoutError::Timeout) => None,
Err(RecvTimeoutError::Disconnected) => break,
}
} else if let Ok(recv) = timer_receiver.recv() {
Some(recv)
} else {
break;
};
let now = Instant::now();
if let Some((runnable, dur)) = recv {
runnables.push(RunnableAfter {
runnable,
instant: now + dur,
});
while let Ok((runnable, dur)) = timer_receiver.try_recv() {
runnables.push(RunnableAfter {
runnable,
instant: now + dur,
})
}
}
while runnables.peek().is_some_and(|entry| entry.instant <= now) {
runnables.pop().unwrap().runnable.run();
}
timeout_dur = runnables.peek().map(|entry| entry.instant - now);
}
});
let main_thread_id = current().id();
Self {
background_sender,
main_sender,
timer_sender,
background_threads,
timer_thread,
parker,
main_thread_id,
event,
}
}
}
impl PlatformDispatcher for WindowsDispatcher {
fn is_main_thread(&self) -> bool {
current().id() == self.main_thread_id
}
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>) {
self.background_sender
.send((runnable, label))
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
.ok();
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender
.send(runnable)
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
.ok();
unsafe { SetEvent(self.event) }.ok();
}
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
self.timer_sender
.send((runnable, duration))
.inspect_err(|e| log::error!("Dispatch failed: {e}"))
.ok();
}
fn tick(&self, _background_only: bool) -> bool {
false
}
fn park(&self) {
self.parker.lock().park();
}
fn unparker(&self) -> parking::Unparker {
self.parker.lock().unparker()
}
}
struct RunnableAfter {
runnable: Runnable,
instant: Instant,
}
impl PartialEq for RunnableAfter {
fn eq(&self, other: &Self) -> bool {
self.instant == other.instant
}
}
impl Eq for RunnableAfter {}
impl Ord for RunnableAfter {
fn cmp(&self, other: &Self) -> Ordering {
self.instant.cmp(&other.instant).reverse()
}
}
impl PartialOrd for RunnableAfter {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

View file

@ -0,0 +1,36 @@
use anyhow::{anyhow, Result};
use uuid::Uuid;
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
#[derive(Debug)]
pub(crate) struct WindowsDisplay;
impl WindowsDisplay {
pub(crate) fn new() -> Self {
Self
}
}
impl PlatformDisplay for WindowsDisplay {
// todo!("windows")
fn id(&self) -> DisplayId {
DisplayId(1)
}
// todo!("windows")
fn uuid(&self) -> Result<Uuid> {
Err(anyhow!("not implemented yet."))
}
// todo!("windows")
fn bounds(&self) -> Bounds<GlobalPixels> {
Bounds::new(
Point::new(0.0.into(), 0.0.into()),
Size {
width: 1920.0.into(),
height: 1280.0.into(),
},
)
}
}

View file

@ -0,0 +1,317 @@
// todo!("windows"): remove
#![allow(unused_variables)]
use std::{
cell::RefCell,
collections::HashSet,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
time::Duration,
};
use anyhow::{anyhow, Result};
use async_task::Runnable;
use futures::channel::oneshot::Receiver;
use parking_lot::Mutex;
use time::UtcOffset;
use util::SemanticVersion;
use windows::Win32::{
Foundation::{CloseHandle, HANDLE, HWND},
System::Threading::{CreateEventW, INFINITE},
UI::WindowsAndMessaging::{
DispatchMessageW, MsgWaitForMultipleObjects, PeekMessageW, PostQuitMessage,
TranslateMessage, MSG, PM_REMOVE, QS_ALLINPUT, WM_QUIT,
},
};
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, WindowsDisplay,
WindowsTextSystem, WindowsWindow,
};
pub(crate) struct WindowsPlatform {
inner: Rc<WindowsPlatformInner>,
}
pub(crate) struct WindowsPlatformInner {
background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
main_receiver: flume::Receiver<Runnable>,
text_system: Arc<WindowsTextSystem>,
callbacks: Mutex<Callbacks>,
pub(crate) window_handles: RefCell<HashSet<AnyWindowHandle>>,
pub(crate) event: HANDLE,
}
impl Drop for WindowsPlatformInner {
fn drop(&mut self) {
unsafe { CloseHandle(self.event) }.ok();
}
}
#[derive(Default)]
struct Callbacks {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
will_open_app_menu: Option<Box<dyn FnMut()>>,
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
impl WindowsPlatform {
pub(crate) fn new() -> Self {
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
let text_system = Arc::new(WindowsTextSystem::new());
let callbacks = Mutex::new(Callbacks::default());
let window_handles = RefCell::new(HashSet::new());
let inner = Rc::new(WindowsPlatformInner {
background_executor,
foreground_executor,
main_receiver,
text_system,
callbacks,
window_handles,
event,
});
Self { inner }
}
}
impl Platform for WindowsPlatform {
fn background_executor(&self) -> BackgroundExecutor {
self.inner.background_executor.clone()
}
fn foreground_executor(&self) -> ForegroundExecutor {
self.inner.foreground_executor.clone()
}
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
self.inner.text_system.clone()
}
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
on_finish_launching();
'a: loop {
unsafe {
MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT)
};
let mut msg = MSG::default();
while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() {
if msg.message == WM_QUIT {
break 'a;
}
unsafe { TranslateMessage(&msg) };
unsafe { DispatchMessageW(&msg) };
}
while let Ok(runnable) = self.inner.main_receiver.try_recv() {
runnable.run();
}
}
let mut callbacks = self.inner.callbacks.lock();
if let Some(callback) = callbacks.quit.as_mut() {
callback()
}
}
fn quit(&self) {
self.foreground_executor()
.spawn(async { unsafe { PostQuitMessage(0) } })
.detach();
}
// todo!("windows")
fn restart(&self) {
unimplemented!()
}
// todo!("windows")
fn activate(&self, ignoring_other_apps: bool) {}
// todo!("windows")
fn hide(&self) {
unimplemented!()
}
// todo!("windows")
fn hide_other_apps(&self) {
unimplemented!()
}
// todo!("windows")
fn unhide_other_apps(&self) {
unimplemented!()
}
// todo!("windows")
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
vec![Rc::new(WindowsDisplay::new())]
}
// todo!("windows")
fn display(&self, id: crate::DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
Some(Rc::new(WindowsDisplay::new()))
}
// todo!("windows")
fn active_window(&self) -> Option<AnyWindowHandle> {
unimplemented!()
}
fn open_window(
&self,
handle: AnyWindowHandle,
options: WindowOptions,
) -> Box<dyn PlatformWindow> {
Box::new(WindowsWindow::new(self.inner.clone(), handle, options))
}
// todo!("windows")
fn window_appearance(&self) -> WindowAppearance {
WindowAppearance::Dark
}
// todo!("windows")
fn open_url(&self, url: &str) {
// todo!("windows")
}
// todo!("windows")
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.inner.callbacks.lock().open_urls = Some(callback);
}
// todo!("windows")
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
unimplemented!()
}
// todo!("windows")
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
unimplemented!()
}
// todo!("windows")
fn reveal_path(&self, path: &Path) {
unimplemented!()
}
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().become_active = Some(callback);
}
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().resign_active = Some(callback);
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().quit = Some(callback);
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().reopen = Some(callback);
}
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.inner.callbacks.lock().event = Some(callback);
}
// todo!("windows")
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.inner.callbacks.lock().app_menu_action = Some(callback);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().will_open_app_menu = Some(callback);
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str {
"Windows"
}
fn os_version(&self) -> Result<SemanticVersion> {
Ok(SemanticVersion {
major: 1,
minor: 0,
patch: 0,
})
}
fn app_version(&self) -> Result<SemanticVersion> {
Ok(SemanticVersion {
major: 1,
minor: 0,
patch: 0,
})
}
// todo!("windows")
fn app_path(&self) -> Result<PathBuf> {
Err(anyhow!("not yet implemented"))
}
// todo!("windows")
fn local_timezone(&self) -> UtcOffset {
UtcOffset::from_hms(9, 0, 0).unwrap()
}
// todo!("windows")
fn double_click_interval(&self) -> Duration {
Duration::from_millis(100)
}
// todo!("windows")
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
Err(anyhow!("not yet implemented"))
}
// todo!("windows")
fn set_cursor_style(&self, style: CursorStyle) {}
// todo!("windows")
fn should_auto_hide_scrollbars(&self) -> bool {
false
}
// todo!("windows")
fn write_to_clipboard(&self, item: ClipboardItem) {
unimplemented!()
}
// todo!("windows")
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
unimplemented!()
}
// todo!("windows")
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
Task::Ready(Some(Err(anyhow!("not implemented yet."))))
}
// todo!("windows")
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
Task::Ready(Some(Err(anyhow!("not implemented yet."))))
}
// todo!("windows")
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
Task::Ready(Some(Err(anyhow!("not implemented yet."))))
}
}

View file

@ -0,0 +1,440 @@
use crate::{
point, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle,
FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams,
ShapedGlyph, SharedString, Size,
};
use anyhow::{anyhow, Context, Ok, Result};
use collections::HashMap;
use cosmic_text::{
fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont,
FontSystem, SwashCache,
};
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use pathfinder_geometry::{
rect::{RectF, RectI},
vector::{Vector2F, Vector2I},
};
use smallvec::SmallVec;
use std::{borrow::Cow, sync::Arc};
pub(crate) struct WindowsTextSystem(RwLock<WindowsTextSystemState>);
struct WindowsTextSystemState {
swash_cache: SwashCache,
font_system: FontSystem,
fonts: Vec<Arc<CosmicTextFont>>,
font_selections: HashMap<Font, FontId>,
font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
postscript_names_by_font_id: HashMap<FontId, String>,
}
impl WindowsTextSystem {
pub(crate) fn new() -> Self {
let mut font_system = FontSystem::new();
// todo!("windows") make font loading non-blocking
font_system.db_mut().load_system_fonts();
Self(RwLock::new(WindowsTextSystemState {
font_system,
swash_cache: SwashCache::new(),
fonts: Vec::new(),
font_selections: HashMap::default(),
// font_ids_by_postscript_name: HashMap::default(),
font_ids_by_family_name: HashMap::default(),
postscript_names_by_font_id: HashMap::default(),
}))
}
}
impl Default for WindowsTextSystem {
fn default() -> Self {
Self::new()
}
}
#[allow(unused)]
impl PlatformTextSystem for WindowsTextSystem {
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
self.0.write().add_fonts(fonts)
}
// todo!("windows") ensure that this integrates with platform font loading
// do we need to do more than call load_system_fonts()?
fn all_font_names(&self) -> Vec<String> {
self.0
.read()
.font_system
.db()
.faces()
.map(|face| face.post_script_name.clone())
.collect()
}
// todo!("windows")
fn all_font_families(&self) -> Vec<String> {
Vec::new()
}
fn font_id(&self, font: &Font) -> Result<FontId> {
// todo!("windows"): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
let lock = self.0.upgradable_read();
if let Some(font_id) = lock.font_selections.get(font) {
Ok(*font_id)
} else {
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
{
font_ids.as_slice()
} else {
let font_ids = lock.load_family(&font.family, font.features)?;
lock.font_ids_by_family_name
.insert(font.family.clone(), font_ids);
lock.font_ids_by_family_name[&font.family].as_ref()
};
let id = lock
.font_system
.db()
.query(&Query {
families: &[Family::Name(&font.family)],
weight: font.weight.into(),
style: font.style.into(),
stretch: Default::default(),
})
.context("no font")?;
let font_id = if let Some(font_id) = lock.fonts.iter().position(|font| font.id() == id)
{
FontId(font_id)
} else {
// Font isn't in fonts so add it there, this is because we query all the fonts in the db
// and maybe we haven't loaded it yet
let font_id = FontId(lock.fonts.len());
let font = lock.font_system.get_font(id).unwrap();
lock.fonts.push(font);
font_id
};
lock.font_selections.insert(font.clone(), font_id);
Ok(font_id)
}
}
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
let metrics = self.0.read().fonts[font_id.0].as_swash().metrics(&[]);
FontMetrics {
units_per_em: metrics.units_per_em as u32,
ascent: metrics.ascent,
descent: -metrics.descent, // todo!("windows") confirm this is correct
line_gap: metrics.leading,
underline_position: metrics.underline_offset,
underline_thickness: metrics.stroke_size,
cap_height: metrics.cap_height,
x_height: metrics.x_height,
// todo!("windows"): Compute this correctly
bounding_box: Bounds {
origin: point(0.0, 0.0),
size: size(metrics.max_width, metrics.ascent + metrics.descent),
},
}
}
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
let lock = self.0.read();
let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]);
let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]);
let glyph_id = glyph_id.0 as u16;
// todo!("windows"): Compute this correctly
// see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620
Ok(Bounds {
origin: point(0.0, 0.0),
size: size(
glyph_metrics.advance_width(glyph_id),
glyph_metrics.advance_height(glyph_id),
),
})
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
self.0.read().advance(font_id, glyph_id)
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
self.0.read().glyph_for_char(font_id, ch)
}
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
self.0.write().raster_bounds(params)
}
fn rasterize_glyph(
&self,
params: &RenderGlyphParams,
raster_bounds: Bounds<DevicePixels>,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
self.0.write().rasterize_glyph(params, raster_bounds)
}
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
self.0.write().layout_line(text, font_size, runs)
}
// todo!("windows") Confirm that this has been superseded by the LineWrapper
fn wrap_line(
&self,
text: &str,
font_id: FontId,
font_size: Pixels,
width: Pixels,
) -> Vec<usize> {
unimplemented!()
}
}
impl WindowsTextSystemState {
#[profiling::function]
fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
let db = self.font_system.db_mut();
for bytes in fonts {
match bytes {
Cow::Borrowed(embedded_font) => {
db.load_font_data(embedded_font.to_vec());
}
Cow::Owned(bytes) => {
db.load_font_data(bytes);
}
}
}
Ok(())
}
#[profiling::function]
fn load_family(
&mut self,
name: &SharedString,
_features: FontFeatures,
) -> Result<SmallVec<[FontId; 4]>> {
let mut font_ids = SmallVec::new();
let family = self
.font_system
.get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name)));
for font in family.as_ref() {
let font = self.font_system.get_font(*font).unwrap();
if font.as_swash().charmap().map('m') == 0 {
self.font_system.db_mut().remove_face(font.id());
continue;
};
let font_id = FontId(self.fonts.len());
font_ids.push(font_id);
self.fonts.push(font);
}
Ok(font_ids)
}
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
let width = self.fonts[font_id.0]
.as_swash()
.glyph_metrics(&[])
.advance_width(glyph_id.0 as u16);
let height = self.fonts[font_id.0]
.as_swash()
.glyph_metrics(&[])
.advance_height(glyph_id.0 as u16);
Ok(Size { width, height })
}
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
let glyph_id = self.fonts[font_id.0].as_swash().charmap().map(ch);
if glyph_id == 0 {
None
} else {
Some(GlyphId(glyph_id.into()))
}
}
fn is_emoji(&self, font_id: FontId) -> bool {
// todo!("windows"): implement this correctly
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
postscript_name == "AppleColorEmoji"
})
}
// todo!("windows") both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let font = &self.fonts[params.font_id.0];
let font_system = &mut self.font_system;
let image = self
.swash_cache
.get_image(
font_system,
CacheKey::new(
font.id(),
params.glyph_id.0 as u16,
(params.font_size * params.scale_factor).into(),
(0.0, 0.0),
)
.0,
)
.clone()
.unwrap();
Ok(Bounds {
origin: point(image.placement.left.into(), (-image.placement.top).into()),
size: size(image.placement.width.into(), image.placement.height.into()),
})
}
#[profiling::function]
fn rasterize_glyph(
&mut self,
params: &RenderGlyphParams,
glyph_bounds: Bounds<DevicePixels>,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
Err(anyhow!("glyph bounds are empty"))
} else {
// todo!("windows") handle subpixel variants
let bitmap_size = glyph_bounds.size;
let font = &self.fonts[params.font_id.0];
let font_system = &mut self.font_system;
let image = self
.swash_cache
.get_image(
font_system,
CacheKey::new(
font.id(),
params.glyph_id.0 as u16,
(params.font_size * params.scale_factor).into(),
(0.0, 0.0),
)
.0,
)
.clone()
.unwrap();
Ok((bitmap_size, image.data))
}
}
// todo!("windows") This is all a quick first pass, maybe we should be using cosmic_text::Buffer
#[profiling::function]
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
let mut attrs_list = AttrsList::new(Attrs::new());
let mut offs = 0;
for run in font_runs {
// todo!("windows") We need to check we are doing utf properly
let font = &self.fonts[run.font_id.0];
let font = self.font_system.db().face(font.id()).unwrap();
attrs_list.add_span(
offs..offs + run.len,
Attrs::new()
.family(Family::Name(&font.families.first().unwrap().0))
.stretch(font.stretch)
.style(font.style)
.weight(font.weight),
);
offs += run.len;
}
let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced);
let layout = line.layout(
&mut self.font_system,
font_size.0,
f32::MAX, // todo!("windows") we don't have a width cause this should technically not be wrapped I believe
cosmic_text::Wrap::None,
);
let mut runs = Vec::new();
// todo!("windows") what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
let layout = layout.first().unwrap();
for glyph in &layout.glyphs {
let font_id = glyph.font_id;
let font_id = FontId(
self.fonts
.iter()
.position(|font| font.id() == font_id)
.unwrap(),
);
let mut glyphs = SmallVec::new();
// todo!("windows") this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
glyphs.push(ShapedGlyph {
id: GlyphId(glyph.glyph_id as u32),
position: point((glyph.x).into(), glyph.y.into()),
index: glyph.start,
is_emoji: self.is_emoji(font_id),
});
runs.push(crate::ShapedRun { font_id, glyphs });
}
LineLayout {
font_size,
width: layout.w.into(),
ascent: layout.max_ascent.into(),
descent: layout.max_descent.into(),
runs,
len: text.len(),
}
}
}
impl From<RectF> for Bounds<f32> {
fn from(rect: RectF) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<RectI> for Bounds<DevicePixels> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
}
}
}
impl From<Vector2I> for Size<DevicePixels> {
fn from(value: Vector2I) -> Self {
size(value.x().into(), value.y().into())
}
}
impl From<RectI> for Bounds<i32> {
fn from(rect: RectI) -> Self {
Bounds {
origin: point(rect.origin_x(), rect.origin_y()),
size: size(rect.width(), rect.height()),
}
}
}
impl From<Point<u32>> for Vector2I {
fn from(size: Point<u32>) -> Self {
Vector2I::new(size.x as i32, size.y as i32)
}
}
impl From<Vector2F> for Size<f32> {
fn from(vec: Vector2F) -> Self {
size(vec.x(), vec.y())
}
}
impl From<FontWeight> for cosmic_text::Weight {
fn from(value: FontWeight) -> Self {
cosmic_text::Weight(value.0 as u16)
}
}
impl From<FontStyle> for cosmic_text::Style {
fn from(style: FontStyle) -> Self {
match style {
FontStyle::Normal => cosmic_text::Style::Normal,
FontStyle::Italic => cosmic_text::Style::Italic,
FontStyle::Oblique => cosmic_text::Style::Oblique,
}
}
}

View file

@ -0,0 +1,26 @@
use windows::Win32::Foundation::{LPARAM, WPARAM};
pub(crate) trait HiLoWord {
fn hiword(&self) -> u16;
fn loword(&self) -> u16;
}
impl HiLoWord for WPARAM {
fn hiword(&self) -> u16 {
((self.0 >> 16) & 0xFFFF) as u16
}
fn loword(&self) -> u16 {
(self.0 & 0xFFFF) as u16
}
}
impl HiLoWord for LPARAM {
fn hiword(&self) -> u16 {
((self.0 >> 16) & 0xFFFF) as u16
}
fn loword(&self) -> u16 {
(self.0 & 0xFFFF) as u16
}
}

View file

@ -0,0 +1,535 @@
#![deny(unsafe_op_in_unsafe_fn)]
// todo!("windows"): remove
#![allow(unused_variables)]
use std::{
any::Any,
cell::{Cell, RefCell},
ffi::c_void,
num::NonZeroIsize,
rc::{Rc, Weak},
sync::{Arc, Once},
};
use blade_graphics as gpu;
use futures::channel::oneshot::Receiver;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use windows::{
core::{w, HSTRING, PCWSTR},
Win32::{
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage,
RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW,
CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WINDOW_EX_STYLE,
WINDOW_LONG_PTR_INDEX, WM_CLOSE, WM_DESTROY, WM_MOVE, WM_NCCREATE, WM_NCDESTROY,
WM_PAINT, WM_SIZE, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE,
},
},
};
use crate::{
platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, Modifiers,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, PromptLevel, Scene, Size, WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay,
WindowsPlatformInner,
};
struct WindowsWindowInner {
hwnd: HWND,
origin: Cell<Point<GlobalPixels>>,
size: Cell<Size<GlobalPixels>>,
mouse_position: Cell<Point<Pixels>>,
input_handler: Cell<Option<PlatformInputHandler>>,
renderer: RefCell<BladeRenderer>,
callbacks: RefCell<Callbacks>,
platform_inner: Rc<WindowsPlatformInner>,
handle: AnyWindowHandle,
}
impl WindowsWindowInner {
fn new(
hwnd: HWND,
cs: &CREATESTRUCTW,
platform_inner: Rc<WindowsPlatformInner>,
handle: AnyWindowHandle,
) -> Self {
let origin = Cell::new(Point::new((cs.x as f64).into(), (cs.y as f64).into()));
let size = Cell::new(Size {
width: (cs.cx as f64).into(),
height: (cs.cy as f64).into(),
});
let mouse_position = Cell::new(Point::default());
let input_handler = Cell::new(None);
struct RawWindow {
hwnd: *mut c_void,
}
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
let mut handle = blade_rwh::Win32WindowHandle::empty();
handle.hwnd = self.hwnd;
handle.into()
}
}
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
blade_rwh::WindowsDisplayHandle::empty().into()
}
}
let raw = RawWindow { hwnd: hwnd.0 as _ };
let gpu = Arc::new(
unsafe {
gpu::Context::init_windowed(
&raw,
gpu::ContextDesc {
validation: false,
capture: false,
},
)
}
.unwrap(),
);
let extent = gpu::Extent {
width: 1,
height: 1,
depth: 1,
};
let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
let callbacks = RefCell::new(Callbacks::default());
Self {
hwnd,
origin,
size,
mouse_position,
input_handler,
renderer,
callbacks,
platform_inner,
handle,
}
}
fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
match msg {
WM_MOVE => {
let x = lparam.loword() as f64;
let y = lparam.hiword() as f64;
self.origin.set(Point::new(x.into(), y.into()));
let mut callbacks = self.callbacks.borrow_mut();
if let Some(callback) = callbacks.moved.as_mut() {
callback()
}
}
WM_SIZE => {
// todo!("windows"): handle maximized or minimized
let width = lparam.loword().max(1) as f64;
let height = lparam.hiword().max(1) as f64;
self.renderer
.borrow_mut()
.update_drawable_size(Size { width, height });
let width = width.into();
let height = height.into();
self.size.set(Size { width, height });
let mut callbacks = self.callbacks.borrow_mut();
if let Some(callback) = callbacks.resize.as_mut() {
callback(
Size {
width: Pixels(width.0),
height: Pixels(height.0),
},
1.0,
)
}
}
WM_PAINT => {
let mut callbacks = self.callbacks.borrow_mut();
if let Some(callback) = callbacks.request_frame.as_mut() {
callback()
}
}
WM_CLOSE => {
let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut();
if let Some(callback) = callbacks.should_close.as_mut() {
if callback() {
return LRESULT(0);
}
}
drop(callbacks);
return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
}
WM_DESTROY => {
let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut();
if let Some(callback) = callbacks.close.take() {
callback()
}
let mut window_handles = self.platform_inner.window_handles.borrow_mut();
window_handles.remove(&self.handle);
if window_handles.is_empty() {
self.platform_inner
.foreground_executor
.spawn(async {
unsafe { PostQuitMessage(0) };
})
.detach();
}
return LRESULT(1);
}
_ => return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
}
LRESULT(0)
}
}
#[derive(Default)]
struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
active_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
fullscreen: Option<Box<dyn FnMut(bool)>>,
moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>,
close: Option<Box<dyn FnOnce()>>,
appearance_changed: Option<Box<dyn FnMut()>>,
}
pub(crate) struct WindowsWindow {
inner: Rc<WindowsWindowInner>,
}
struct WindowCreateContext {
inner: Option<Rc<WindowsWindowInner>>,
platform_inner: Rc<WindowsPlatformInner>,
handle: AnyWindowHandle,
}
impl WindowsWindow {
pub(crate) fn new(
platform_inner: Rc<WindowsPlatformInner>,
handle: AnyWindowHandle,
options: WindowOptions,
) -> Self {
let dwexstyle = WINDOW_EX_STYLE::default();
let classname = register_wnd_class();
let windowname = HSTRING::from(
options
.titlebar
.as_ref()
.and_then(|titlebar| titlebar.title.as_ref())
.map(|title| title.as_ref())
.unwrap_or(""),
);
let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE;
let mut x = CW_USEDEFAULT;
let mut y = CW_USEDEFAULT;
let mut nwidth = CW_USEDEFAULT;
let mut nheight = CW_USEDEFAULT;
match options.bounds {
WindowBounds::Fullscreen => {}
WindowBounds::Maximized => {}
WindowBounds::Fixed(bounds) => {
x = bounds.origin.x.0 as i32;
y = bounds.origin.y.0 as i32;
nwidth = bounds.size.width.0 as i32;
nheight = bounds.size.height.0 as i32;
}
};
let hwndparent = HWND::default();
let hmenu = HMENU::default();
let hinstance = HINSTANCE::default();
let mut context = WindowCreateContext {
inner: None,
platform_inner: platform_inner.clone(),
handle,
};
let lpparam = Some(&context as *const _ as *const _);
unsafe {
CreateWindowExW(
dwexstyle,
classname,
&windowname,
dwstyle,
x,
y,
nwidth,
nheight,
hwndparent,
hmenu,
hinstance,
lpparam,
)
};
let wnd = Self {
inner: context.inner.unwrap(),
};
platform_inner.window_handles.borrow_mut().insert(handle);
match options.bounds {
WindowBounds::Fullscreen => wnd.toggle_full_screen(),
WindowBounds::Maximized => wnd.maximize(),
WindowBounds::Fixed(_) => {}
}
unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
wnd
}
fn maximize(&self) {
unsafe { ShowWindow(self.inner.hwnd, SW_MAXIMIZE) };
}
}
impl HasWindowHandle for WindowsWindow {
fn window_handle(
&self,
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
NonZeroIsize::new_unchecked(self.inner.hwnd.0)
})
.into();
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
}
}
// todo!("windows")
impl HasDisplayHandle for WindowsWindow {
fn display_handle(
&self,
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
unimplemented!()
}
}
impl PlatformWindow for WindowsWindow {
fn bounds(&self) -> WindowBounds {
WindowBounds::Fixed(Bounds {
origin: self.inner.origin.get(),
size: self.inner.size.get(),
})
}
// todo!("windows")
fn content_size(&self) -> Size<Pixels> {
let size = self.inner.size.get();
Size {
width: size.width.0.into(),
height: size.height.0.into(),
}
}
// todo!("windows")
fn scale_factor(&self) -> f32 {
1.0
}
// todo!("windows")
fn titlebar_height(&self) -> Pixels {
20.0.into()
}
// todo!("windows")
fn appearance(&self) -> WindowAppearance {
WindowAppearance::Dark
}
// todo!("windows")
fn display(&self) -> Rc<dyn PlatformDisplay> {
Rc::new(WindowsDisplay::new())
}
fn mouse_position(&self) -> Point<Pixels> {
self.inner.mouse_position.get()
}
// todo!("windows")
fn modifiers(&self) -> Modifiers {
Modifiers::none()
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
// todo!("windows")
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.inner.input_handler.set(Some(input_handler));
}
// todo!("windows")
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.inner.input_handler.take()
}
// todo!("windows")
fn prompt(
&self,
level: PromptLevel,
msg: &str,
detail: Option<&str>,
answers: &[&str],
) -> Receiver<usize> {
unimplemented!()
}
// todo!("windows")
fn activate(&self) {}
// todo!("windows")
fn set_title(&mut self, title: &str) {
unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
.inspect_err(|e| log::error!("Set title failed: {e}"))
.ok();
}
// todo!("windows")
fn set_edited(&mut self, edited: bool) {}
// todo!("windows")
fn show_character_palette(&self) {}
// todo!("windows")
fn minimize(&self) {}
// todo!("windows")
fn zoom(&self) {}
// todo!("windows")
fn toggle_full_screen(&self) {}
// todo!("windows")
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().request_frame = Some(callback);
}
// todo!("windows")
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.inner.callbacks.borrow_mut().input = Some(callback);
}
// todo!("windows")
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
}
// todo!("windows")
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.inner.callbacks.borrow_mut().resize = Some(callback);
}
// todo!("windows")
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
self.inner.callbacks.borrow_mut().fullscreen = Some(callback);
}
// todo!("windows")
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().moved = Some(callback);
}
// todo!("windows")
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
self.inner.callbacks.borrow_mut().should_close = Some(callback);
}
// todo!("windows")
fn on_close(&self, callback: Box<dyn FnOnce()>) {
self.inner.callbacks.borrow_mut().close = Some(callback);
}
// todo!("windows")
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
}
// todo!("windows")
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
true
}
// todo!("windows")
fn draw(&self, scene: &Scene) {
self.inner.renderer.borrow_mut().draw(scene)
}
// todo!("windows")
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.inner.renderer.borrow().sprite_atlas().clone()
}
}
fn register_wnd_class() -> PCWSTR {
const CLASS_NAME: PCWSTR = w!("Zed::Window");
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let wc = WNDCLASSW {
lpfnWndProc: Some(wnd_proc),
hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() },
lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
..Default::default()
};
unsafe { RegisterClassW(&wc) };
});
CLASS_NAME
}
unsafe extern "system" fn wnd_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if msg == WM_NCCREATE {
let cs = lparam.0 as *const CREATESTRUCTW;
let cs = unsafe { &*cs };
let ctx = cs.lpCreateParams as *mut WindowCreateContext;
let ctx = unsafe { &mut *ctx };
let inner = Rc::new(WindowsWindowInner::new(
hwnd,
cs,
ctx.platform_inner.clone(),
ctx.handle,
));
let weak = Box::new(Rc::downgrade(&inner));
unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
ctx.inner = Some(inner);
return LRESULT(1);
}
let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
if ptr.is_null() {
return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
}
let inner = unsafe { &*ptr };
let r = if let Some(inner) = inner.upgrade() {
inner.handle_msg(msg, wparam, lparam)
} else {
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
};
if msg == WM_NCDESTROY {
unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
unsafe { std::mem::drop(Box::from_raw(ptr)) };
}
r
}
unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
#[cfg(target_pointer_width = "64")]
unsafe {
GetWindowLongPtrW(hwnd, nindex)
}
#[cfg(target_pointer_width = "32")]
unsafe {
GetWindowLongW(hwnd, nindex) as isize
}
}
unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize {
#[cfg(target_pointer_width = "64")]
unsafe {
SetWindowLongPtrW(hwnd, nindex, dwnewlong)
}
#[cfg(target_pointer_width = "32")]
unsafe {
SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize
}
}

View file

@ -43,4 +43,9 @@ fn main() {
}
}
}
// todo!("windows"): This is to avoid stack overflow. Remove it when solved.
if std::env::var("CARGO_CFG_TARGET_ENV").ok() == Some("msvc".to_string()) {
println!("cargo:rustc-link-arg=/stack:{}", 8 * 1024 * 1024);
}
}