Detailed changes
@@ -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"
@@ -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"
@@ -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 {
@@ -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::*;
@@ -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))
+ }
+}
@@ -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(),
+ },
+ )
+ }
+}
@@ -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."))))
+ }
+}
@@ -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,
+ }
+ }
+}
@@ -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
+ }
+}
@@ -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
+ }
+}
@@ -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);
+ }
}