mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Cross-platform titlebar (#9405)
This PR reverts https://github.com/zed-industries/zed/pull/9392 and fixes the regressions that led to the reversion. Release Notes: - N/A --------- Co-authored-by: Ezekiel Warren <ezekiel@seaube.com>
This commit is contained in:
parent
44ac6ca45c
commit
328aa2cc95
11
Cargo.toml
11
Cargo.toml
@ -343,6 +343,17 @@ features = [
|
|||||||
"Win32_Globalization",
|
"Win32_Globalization",
|
||||||
"Win32_Graphics_DirectComposition",
|
"Win32_Graphics_DirectComposition",
|
||||||
"Win32_Graphics_Gdi",
|
"Win32_Graphics_Gdi",
|
||||||
|
"Win32_UI_Controls",
|
||||||
|
"Win32_Graphics_DirectWrite",
|
||||||
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
"Win32_System_Com",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
|
"Win32_UI_Controls",
|
||||||
|
"Win32_System_SystemInformation",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
|
"Win32_System_Time",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
|
@ -13,12 +13,12 @@ use rpc::proto;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike,
|
h_flex, platform_titlebar, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator,
|
||||||
ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip,
|
Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
|
use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
|
||||||
use workspace::{notifications::NotifyResultExt, titlebar_height, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
const MAX_PROJECT_NAME_LENGTH: usize = 40;
|
||||||
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
const MAX_BRANCH_NAME_LENGTH: usize = 40;
|
||||||
@ -58,26 +58,17 @@ impl Render for CollabTitlebarItem {
|
|||||||
let project_id = self.project.read(cx).remote_id();
|
let project_id = self.project.read(cx).remote_id();
|
||||||
let workspace = self.workspace.upgrade();
|
let workspace = self.workspace.upgrade();
|
||||||
|
|
||||||
h_flex()
|
platform_titlebar("collab-titlebar")
|
||||||
.id("titlebar")
|
.titlebar_bg(cx.theme().colors().title_bar_background)
|
||||||
|
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||||
|
.when(cfg!(not(windows)), |this| {
|
||||||
|
this.on_click(|event, cx| {
|
||||||
|
if event.up.click_count == 2 {
|
||||||
|
cx.zoom_window();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.w_full()
|
|
||||||
.h(titlebar_height(cx))
|
|
||||||
.map(|this| {
|
|
||||||
if cx.is_fullscreen() {
|
|
||||||
this.pl_2()
|
|
||||||
} else {
|
|
||||||
// Use pixels here instead of a rem-based size because the macOS traffic
|
|
||||||
// lights are a static size, and don't scale with the rest of the UI.
|
|
||||||
this.pl(px(80.))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.bg(cx.theme().colors().title_bar_background)
|
|
||||||
.on_click(|event, cx| {
|
|
||||||
if event.up.click_count == 2 {
|
|
||||||
cx.zoom_window();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// left side
|
// left side
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// todo(linux): remove
|
// todo(linux): remove
|
||||||
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
||||||
// todo("windows"): remove
|
// todo(windows): remove
|
||||||
#![cfg_attr(windows, allow(dead_code))]
|
#![cfg_attr(windows, allow(dead_code))]
|
||||||
|
|
||||||
mod app_menu;
|
mod app_menu;
|
||||||
@ -22,10 +22,10 @@ mod test;
|
|||||||
mod windows;
|
mod windows;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
|
||||||
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels,
|
||||||
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
|
GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams,
|
||||||
SharedString, Size, Task, TaskLabel, WindowContext,
|
RenderImageParams, RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
@ -170,9 +170,9 @@ unsafe impl Send for DisplayId {}
|
|||||||
|
|
||||||
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels>;
|
fn bounds(&self) -> Bounds<GlobalPixels>;
|
||||||
|
fn is_maximized(&self) -> bool;
|
||||||
fn content_size(&self) -> Size<Pixels>;
|
fn content_size(&self) -> Size<Pixels>;
|
||||||
fn scale_factor(&self) -> f32;
|
fn scale_factor(&self) -> f32;
|
||||||
fn titlebar_height(&self) -> Pixels;
|
|
||||||
fn appearance(&self) -> WindowAppearance;
|
fn appearance(&self) -> WindowAppearance;
|
||||||
fn display(&self) -> Rc<dyn PlatformDisplay>;
|
fn display(&self) -> Rc<dyn PlatformDisplay>;
|
||||||
fn mouse_position(&self) -> Point<Pixels>;
|
fn mouse_position(&self) -> Point<Pixels>;
|
||||||
@ -196,7 +196,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||||||
fn toggle_fullscreen(&self);
|
fn toggle_fullscreen(&self);
|
||||||
fn is_fullscreen(&self) -> bool;
|
fn is_fullscreen(&self) -> bool;
|
||||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
|
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
|
||||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
||||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
||||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
|
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
|
||||||
@ -206,9 +206,11 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||||
fn draw(&self, scene: &Scene);
|
fn draw(&self, scene: &Scene);
|
||||||
|
|
||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_raw_handle(&self) -> windows::HWND;
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||||
None
|
None
|
||||||
|
@ -29,7 +29,7 @@ use crate::{
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct Callbacks {
|
pub(crate) struct Callbacks {
|
||||||
request_frame: Option<Box<dyn FnMut()>>,
|
request_frame: Option<Box<dyn FnMut()>>,
|
||||||
input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
|
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
|
||||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
||||||
@ -237,7 +237,7 @@ impl WaylandWindowState {
|
|||||||
|
|
||||||
pub fn handle_input(&self, input: PlatformInput) {
|
pub fn handle_input(&self, input: PlatformInput) {
|
||||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||||
if fun(input.clone()) {
|
if !fun(input.clone()).propagate {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,6 +279,11 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(linux)
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
let inner = self.0.inner.borrow_mut();
|
let inner = self.0.inner.borrow_mut();
|
||||||
Size {
|
Size {
|
||||||
@ -291,11 +296,6 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
self.0.inner.borrow_mut().scale
|
self.0.inner.borrow_mut().scale
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(linux)
|
|
||||||
fn titlebar_height(&self) -> Pixels {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo(linux)
|
// todo(linux)
|
||||||
fn appearance(&self) -> WindowAppearance {
|
fn appearance(&self) -> WindowAppearance {
|
||||||
WindowAppearance::Light
|
WindowAppearance::Light
|
||||||
@ -378,7 +378,7 @@ impl PlatformWindow for WaylandWindow {
|
|||||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
||||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ use super::X11Display;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Callbacks {
|
struct Callbacks {
|
||||||
request_frame: Option<Box<dyn FnMut()>>,
|
request_frame: Option<Box<dyn FnMut()>>,
|
||||||
input: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
||||||
@ -303,7 +303,7 @@ impl X11WindowState {
|
|||||||
|
|
||||||
pub fn handle_input(&self, input: PlatformInput) {
|
pub fn handle_input(&self, input: PlatformInput) {
|
||||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||||
if fun(input.clone()) {
|
if !fun(input.clone()).propagate {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,6 +333,11 @@ impl PlatformWindow for X11Window {
|
|||||||
.map(|v| GlobalPixels(v as f32))
|
.map(|v| GlobalPixels(v as f32))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo(linux)
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.0.inner.borrow_mut().content_size()
|
self.0.inner.borrow_mut().content_size()
|
||||||
}
|
}
|
||||||
@ -341,11 +346,6 @@ impl PlatformWindow for X11Window {
|
|||||||
self.0.inner.borrow_mut().scale_factor
|
self.0.inner.borrow_mut().scale_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(linux)
|
|
||||||
fn titlebar_height(&self) -> Pixels {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo(linux)
|
// todo(linux)
|
||||||
fn appearance(&self) -> WindowAppearance {
|
fn appearance(&self) -> WindowAppearance {
|
||||||
WindowAppearance::Light
|
WindowAppearance::Light
|
||||||
@ -451,7 +451,7 @@ impl PlatformWindow for X11Window {
|
|||||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
||||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,10 +182,6 @@ impl platform::Window for StatusItem {
|
|||||||
self.0.borrow().scale_factor()
|
self.0.borrow().scale_factor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_height(&self) -> f32 {
|
|
||||||
0.
|
|
||||||
}
|
|
||||||
|
|
||||||
fn appearance(&self) -> platform::Appearance {
|
fn appearance(&self) -> platform::Appearance {
|
||||||
unsafe {
|
unsafe {
|
||||||
let appearance: id =
|
let appearance: id =
|
||||||
|
@ -324,7 +324,7 @@ struct MacWindowState {
|
|||||||
renderer: renderer::Renderer,
|
renderer: renderer::Renderer,
|
||||||
kind: WindowKind,
|
kind: WindowKind,
|
||||||
request_frame_callback: Option<Box<dyn FnMut()>>,
|
request_frame_callback: Option<Box<dyn FnMut()>>,
|
||||||
event_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
event_callback: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
|
||||||
activate_callback: Option<Box<dyn FnMut(bool)>>,
|
activate_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
|
fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
@ -411,6 +411,14 @@ impl MacWindowState {
|
|||||||
self.display_link = None;
|
self.display_link = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
let bounds = self.bounds();
|
||||||
|
let screen_size = self.native_window.screen().visibleFrame().into();
|
||||||
|
bounds.size == screen_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_fullscreen(&self) -> bool {
|
fn is_fullscreen(&self) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
let style_mask = self.native_window.styleMask();
|
let style_mask = self.native_window.styleMask();
|
||||||
@ -716,6 +724,10 @@ impl PlatformWindow for MacWindow {
|
|||||||
self.0.as_ref().lock().bounds()
|
self.0.as_ref().lock().bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
self.0.as_ref().lock().is_maximized()
|
||||||
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.0.as_ref().lock().content_size()
|
self.0.as_ref().lock().content_size()
|
||||||
}
|
}
|
||||||
@ -724,10 +736,6 @@ impl PlatformWindow for MacWindow {
|
|||||||
self.0.as_ref().lock().scale_factor()
|
self.0.as_ref().lock().scale_factor()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_height(&self) -> Pixels {
|
|
||||||
self.0.as_ref().lock().titlebar_height()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn appearance(&self) -> WindowAppearance {
|
fn appearance(&self) -> WindowAppearance {
|
||||||
unsafe {
|
unsafe {
|
||||||
let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
|
let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
|
||||||
@ -968,7 +976,7 @@ impl PlatformWindow for MacWindow {
|
|||||||
self.0.as_ref().lock().request_frame_callback = Some(callback);
|
self.0.as_ref().lock().request_frame_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
|
||||||
self.0.as_ref().lock().event_callback = Some(callback);
|
self.0.as_ref().lock().event_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1191,7 +1199,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
|
|||||||
window_state.lock().previous_keydown_inserted_text = Some(text.clone());
|
window_state.lock().previous_keydown_inserted_text = Some(text.clone());
|
||||||
if let Some(callback) = callback.as_mut() {
|
if let Some(callback) = callback.as_mut() {
|
||||||
event.keystroke.ime_key = Some(text.clone());
|
event.keystroke.ime_key = Some(text.clone());
|
||||||
handled = callback(PlatformInput::KeyDown(event));
|
handled = !callback(PlatformInput::KeyDown(event)).propagate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1204,7 +1212,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
|
|||||||
let is_held = event.is_held;
|
let is_held = event.is_held;
|
||||||
|
|
||||||
if let Some(callback) = callback.as_mut() {
|
if let Some(callback) = callback.as_mut() {
|
||||||
handled = callback(PlatformInput::KeyDown(event));
|
handled = !callback(PlatformInput::KeyDown(event)).propagate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !handled && is_held {
|
if !handled && is_held {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, GlobalPixels, Pixels,
|
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult,
|
||||||
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
|
GlobalPixels, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||||
Size, TestPlatform, TileId, WindowAppearance, WindowParams,
|
PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowParams,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
@ -20,7 +20,7 @@ pub(crate) struct TestWindowState {
|
|||||||
platform: Weak<TestPlatform>,
|
platform: Weak<TestPlatform>,
|
||||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
|
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
|
||||||
input_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
|
||||||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
moved_callback: Option<Box<dyn FnMut()>>,
|
moved_callback: Option<Box<dyn FnMut()>>,
|
||||||
@ -102,7 +102,7 @@ impl TestWindow {
|
|||||||
drop(lock);
|
drop(lock);
|
||||||
let result = callback(event);
|
let result = callback(event);
|
||||||
self.0.lock().input_callback = Some(callback);
|
self.0.lock().input_callback = Some(callback);
|
||||||
result
|
!result.propagate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,6 +111,10 @@ impl PlatformWindow for TestWindow {
|
|||||||
self.0.lock().bounds
|
self.0.lock().bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.bounds().size.into()
|
self.bounds().size.into()
|
||||||
}
|
}
|
||||||
@ -119,10 +123,6 @@ impl PlatformWindow for TestWindow {
|
|||||||
2.0
|
2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_height(&self) -> Pixels {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn appearance(&self) -> WindowAppearance {
|
fn appearance(&self) -> WindowAppearance {
|
||||||
WindowAppearance::Light
|
WindowAppearance::Light
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ impl PlatformWindow for TestWindow {
|
|||||||
|
|
||||||
fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
|
fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
|
||||||
|
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
|
fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>) {
|
||||||
self.0.lock().input_callback = Some(callback)
|
self.0.lock().input_callback = Some(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,6 +249,11 @@ impl PlatformWindow for TestWindow {
|
|||||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||||
Some(self)
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct TestAtlasState {
|
pub(crate) struct TestAtlasState {
|
||||||
|
@ -11,3 +11,5 @@ pub(crate) use platform::*;
|
|||||||
pub(crate) use text_system::*;
|
pub(crate) use text_system::*;
|
||||||
pub(crate) use util::*;
|
pub(crate) use util::*;
|
||||||
pub(crate) use window::*;
|
pub(crate) use window::*;
|
||||||
|
|
||||||
|
pub(crate) use windows::Win32::Foundation::HWND;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use std::rc::Rc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use windows::Win32::{Foundation::*, Graphics::Gdi::*};
|
use windows::Win32::{Foundation::*, Graphics::Gdi::*};
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context, Ok, Result};
|
use anyhow::{anyhow, Context, Ok, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use cosmic_text::Font as CosmicTextFont;
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont,
|
fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, FontSystem, SwashCache,
|
||||||
FontSystem, SwashCache,
|
|
||||||
};
|
};
|
||||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||||
use pathfinder_geometry::{
|
use pathfinder_geometry::{
|
||||||
@ -31,10 +31,6 @@ struct WindowsTextSystemState {
|
|||||||
impl WindowsTextSystem {
|
impl WindowsTextSystem {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let mut font_system = FontSystem::new();
|
let mut font_system = FontSystem::new();
|
||||||
|
|
||||||
// todo(windows) make font loading non-blocking
|
|
||||||
font_system.db_mut().load_system_fonts();
|
|
||||||
|
|
||||||
Self(RwLock::new(WindowsTextSystemState {
|
Self(RwLock::new(WindowsTextSystemState {
|
||||||
font_system,
|
font_system,
|
||||||
swash_cache: SwashCache::new(),
|
swash_cache: SwashCache::new(),
|
||||||
@ -222,10 +218,11 @@ impl WindowsTextSystemState {
|
|||||||
.get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name)));
|
.get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name)));
|
||||||
for font in family.as_ref() {
|
for font in family.as_ref() {
|
||||||
let font = self.font_system.get_font(*font).unwrap();
|
let font = self.font_system.get_font(*font).unwrap();
|
||||||
if font.as_swash().charmap().map('m') == 0 {
|
// TODO: figure out why this is causing fluent icons from loading
|
||||||
self.font_system.db_mut().remove_face(font.id());
|
// if font.as_swash().charmap().map('m') == 0 {
|
||||||
continue;
|
// self.font_system.db_mut().remove_face(font.id());
|
||||||
};
|
// continue;
|
||||||
|
// };
|
||||||
|
|
||||||
let font_id = FontId(self.fonts.len());
|
let font_id = FontId(self.fonts.len());
|
||||||
font_ids.push(font_id);
|
font_ids.push(font_id);
|
||||||
|
@ -42,3 +42,29 @@ impl HiLoWord for LPARAM {
|
|||||||
(self.0 & 0xFFFF) as i16
|
(self.0 & 0xFFFF) as i16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
// todo(windows): remove
|
|
||||||
#![allow(unused_variables)]
|
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
@ -14,6 +12,7 @@ use std::{
|
|||||||
sync::{Arc, Once},
|
sync::{Arc, Once},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ::util::ResultExt;
|
||||||
use blade_graphics as gpu;
|
use blade_graphics as gpu;
|
||||||
use futures::channel::oneshot::{self, Receiver};
|
use futures::channel::oneshot::{self, Receiver};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -28,6 +27,7 @@ use windows::{
|
|||||||
System::{Com::*, Ole::*, SystemServices::*},
|
System::{Com::*, Ole::*, SystemServices::*},
|
||||||
UI::{
|
UI::{
|
||||||
Controls::*,
|
Controls::*,
|
||||||
|
HiDpi::*,
|
||||||
Input::{Ime::*, KeyboardAndMouse::*},
|
Input::{Ime::*, KeyboardAndMouse::*},
|
||||||
Shell::*,
|
Shell::*,
|
||||||
WindowsAndMessaging::*,
|
WindowsAndMessaging::*,
|
||||||
@ -48,6 +48,7 @@ pub(crate) struct WindowsWindowInner {
|
|||||||
callbacks: RefCell<Callbacks>,
|
callbacks: RefCell<Callbacks>,
|
||||||
platform_inner: Rc<WindowsPlatformInner>,
|
platform_inner: Rc<WindowsPlatformInner>,
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
|
scale_factor: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsWindowInner {
|
impl WindowsWindowInner {
|
||||||
@ -110,9 +111,48 @@ impl WindowsWindowInner {
|
|||||||
callbacks,
|
callbacks,
|
||||||
platform_inner,
|
platform_inner,
|
||||||
handle,
|
handle,
|
||||||
|
scale_factor: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
let mut placement = WINDOWPLACEMENT::default();
|
||||||
|
placement.length = std::mem::size_of::<WINDOWPLACEMENT>() as u32;
|
||||||
|
if unsafe { GetWindowPlacement(self.hwnd, &mut placement) }.is_ok() {
|
||||||
|
return placement.showCmd == SW_SHOWMAXIMIZED.0 as u32;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
|
||||||
|
let top_and_bottom_borders = 2;
|
||||||
|
let theme = unsafe { OpenThemeData(self.hwnd, w!("WINDOW")) };
|
||||||
|
let title_bar_size = unsafe {
|
||||||
|
GetThemePartSize(
|
||||||
|
theme,
|
||||||
|
HDC::default(),
|
||||||
|
WP_CAPTION.0,
|
||||||
|
CS_ACTIVE.0,
|
||||||
|
None,
|
||||||
|
TS_TRUE,
|
||||||
|
)
|
||||||
|
}?;
|
||||||
|
unsafe { CloseThemeData(theme) }?;
|
||||||
|
|
||||||
|
let mut height =
|
||||||
|
(title_bar_size.cy as f32 * self.scale_factor).round() as i32 + top_and_bottom_borders;
|
||||||
|
|
||||||
|
if self.is_maximized() {
|
||||||
|
let dpi = unsafe { GetDpiForWindow(self.hwnd) };
|
||||||
|
height += unsafe { (GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2) as i32 };
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rect = RECT::default();
|
||||||
|
unsafe { GetClientRect(self.hwnd, &mut rect) }?;
|
||||||
|
rect.bottom = rect.top + height;
|
||||||
|
Ok(rect)
|
||||||
|
}
|
||||||
|
|
||||||
fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
|
fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
|
||||||
unsafe { GetKeyState(vkey.0 as i32) < 0 }
|
unsafe { GetKeyState(vkey.0 as i32) < 0 }
|
||||||
}
|
}
|
||||||
@ -136,12 +176,30 @@ impl WindowsWindowInner {
|
|||||||
fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
|
log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
|
||||||
match msg {
|
match msg {
|
||||||
|
WM_ACTIVATE => self.handle_activate_msg(msg, wparam, lparam),
|
||||||
|
WM_CREATE => self.handle_create_msg(lparam),
|
||||||
WM_MOVE => self.handle_move_msg(lparam),
|
WM_MOVE => self.handle_move_msg(lparam),
|
||||||
WM_SIZE => self.handle_size_msg(lparam),
|
WM_SIZE => self.handle_size_msg(lparam),
|
||||||
|
WM_NCCALCSIZE => self.handle_calc_client_size(msg, wparam, lparam),
|
||||||
|
WM_DPICHANGED => self.handle_dpi_changed_msg(msg, wparam, lparam),
|
||||||
|
WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
|
||||||
WM_PAINT => self.handle_paint_msg(),
|
WM_PAINT => self.handle_paint_msg(),
|
||||||
WM_CLOSE => self.handle_close_msg(msg, wparam, lparam),
|
WM_CLOSE => self.handle_close_msg(msg, wparam, lparam),
|
||||||
WM_DESTROY => self.handle_destroy_msg(),
|
WM_DESTROY => self.handle_destroy_msg(),
|
||||||
WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
|
WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
|
||||||
|
WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(msg, wparam, lparam),
|
||||||
|
WM_NCLBUTTONDOWN => {
|
||||||
|
self.handle_nc_mouse_down_msg(MouseButton::Left, msg, wparam, lparam)
|
||||||
|
}
|
||||||
|
WM_NCRBUTTONDOWN => {
|
||||||
|
self.handle_nc_mouse_down_msg(MouseButton::Right, msg, wparam, lparam)
|
||||||
|
}
|
||||||
|
WM_NCMBUTTONDOWN => {
|
||||||
|
self.handle_nc_mouse_down_msg(MouseButton::Middle, msg, wparam, lparam)
|
||||||
|
}
|
||||||
|
WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, msg, wparam, lparam),
|
||||||
|
WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, msg, wparam, lparam),
|
||||||
|
WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, msg, wparam, lparam),
|
||||||
WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
|
WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
|
||||||
WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
|
WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
|
||||||
WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
|
WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
|
||||||
@ -224,7 +282,7 @@ impl WindowsWindowInner {
|
|||||||
|
|
||||||
fn handle_paint_msg(&self) -> LRESULT {
|
fn handle_paint_msg(&self) -> LRESULT {
|
||||||
let mut paint_struct = PAINTSTRUCT::default();
|
let mut paint_struct = PAINTSTRUCT::default();
|
||||||
let hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
|
let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
|
||||||
let mut callbacks = self.callbacks.borrow_mut();
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
||||||
request_frame();
|
request_frame();
|
||||||
@ -291,7 +349,7 @@ impl WindowsWindowInner {
|
|||||||
pressed_button,
|
pressed_button,
|
||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::MouseMove(event)) {
|
if callback(PlatformInput::MouseMove(event)).default_prevented {
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -417,7 +475,7 @@ impl WindowsWindowInner {
|
|||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
};
|
};
|
||||||
if func(PlatformInput::KeyDown(event)) {
|
if func(PlatformInput::KeyDown(event)).default_prevented {
|
||||||
self.invalidate_client_area();
|
self.invalidate_client_area();
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
@ -434,14 +492,14 @@ impl WindowsWindowInner {
|
|||||||
return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
|
return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) };
|
||||||
};
|
};
|
||||||
let event = KeyUpEvent { keystroke };
|
let event = KeyUpEvent { keystroke };
|
||||||
if func(PlatformInput::KeyUp(event)) {
|
if func(PlatformInput::KeyUp(event)).default_prevented {
|
||||||
self.invalidate_client_area();
|
self.invalidate_client_area();
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }
|
unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keydown_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
|
let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
|
||||||
return LRESULT(1);
|
return LRESULT(1);
|
||||||
};
|
};
|
||||||
@ -452,14 +510,14 @@ impl WindowsWindowInner {
|
|||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
};
|
};
|
||||||
if func(PlatformInput::KeyDown(event)) {
|
if func(PlatformInput::KeyDown(event)).default_prevented {
|
||||||
self.invalidate_client_area();
|
self.invalidate_client_area();
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
LRESULT(1)
|
LRESULT(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keyup_msg(&self, message: u32, wparam: WPARAM) -> LRESULT {
|
fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> LRESULT {
|
||||||
let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
|
let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
|
||||||
return LRESULT(1);
|
return LRESULT(1);
|
||||||
};
|
};
|
||||||
@ -467,14 +525,14 @@ impl WindowsWindowInner {
|
|||||||
return LRESULT(1);
|
return LRESULT(1);
|
||||||
};
|
};
|
||||||
let event = KeyUpEvent { keystroke };
|
let event = KeyUpEvent { keystroke };
|
||||||
if func(PlatformInput::KeyUp(event)) {
|
if func(PlatformInput::KeyUp(event)).default_prevented {
|
||||||
self.invalidate_client_area();
|
self.invalidate_client_area();
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
LRESULT(1)
|
LRESULT(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_char_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
|
let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
|
||||||
return LRESULT(1);
|
return LRESULT(1);
|
||||||
};
|
};
|
||||||
@ -487,7 +545,7 @@ impl WindowsWindowInner {
|
|||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
};
|
};
|
||||||
if func(PlatformInput::KeyDown(event)) {
|
if func(PlatformInput::KeyDown(event)).default_prevented {
|
||||||
self.invalidate_client_area();
|
self.invalidate_client_area();
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
@ -515,7 +573,7 @@ impl WindowsWindowInner {
|
|||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
click_count: 1,
|
click_count: 1,
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::MouseDown(event)) {
|
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -533,7 +591,7 @@ impl WindowsWindowInner {
|
|||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
click_count: 1,
|
click_count: 1,
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::MouseUp(event)) {
|
if callback(PlatformInput::MouseUp(event)).default_prevented {
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,7 +636,7 @@ impl WindowsWindowInner {
|
|||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
touch_phase: TouchPhase::Moved,
|
touch_phase: TouchPhase::Moved,
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::ScrollWheel(event)) {
|
if callback(PlatformInput::ScrollWheel(event)).default_prevented {
|
||||||
return LRESULT(0);
|
return LRESULT(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -685,12 +743,237 @@ impl WindowsWindowInner {
|
|||||||
};
|
};
|
||||||
func(input);
|
func(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
|
||||||
|
fn handle_calc_client_size(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
if wparam.0 == 0 {
|
||||||
|
return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let dpi = unsafe { GetDpiForWindow(self.hwnd) };
|
||||||
|
|
||||||
|
let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
|
||||||
|
let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
|
||||||
|
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
|
||||||
|
|
||||||
|
// wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
|
||||||
|
let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
|
||||||
|
let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
|
||||||
|
|
||||||
|
requested_client_rect[0].right -= frame_x + padding;
|
||||||
|
requested_client_rect[0].left += frame_x + padding;
|
||||||
|
requested_client_rect[0].bottom -= frame_y + padding;
|
||||||
|
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_activate_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
|
||||||
|
unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
|
||||||
|
}
|
||||||
|
return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_create_msg(&self, _lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut size_rect = RECT::default();
|
||||||
|
unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
|
||||||
|
let width = size_rect.right - size_rect.left;
|
||||||
|
let height = size_rect.bottom - size_rect.top;
|
||||||
|
|
||||||
|
self.size.set(Size {
|
||||||
|
width: GlobalPixels::from(width as f64),
|
||||||
|
height: GlobalPixels::from(height as f64),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inform the application of the frame change to force redrawing with the new
|
||||||
|
// client area that is extended into the title bar
|
||||||
|
unsafe {
|
||||||
|
SetWindowPos(
|
||||||
|
self.hwnd,
|
||||||
|
HWND::default(),
|
||||||
|
size_rect.left,
|
||||||
|
size_rect.top,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
|
||||||
|
)
|
||||||
|
.log_err()
|
||||||
|
};
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_dpi_changed_msg(&self, _msg: u32, _wparam: WPARAM, _lparam: LPARAM) -> LRESULT {
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
// default handler for resize areas
|
||||||
|
let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
|
||||||
|
if matches!(
|
||||||
|
hit.0 as u32,
|
||||||
|
HTNOWHERE
|
||||||
|
| HTRIGHT
|
||||||
|
| HTLEFT
|
||||||
|
| HTTOPLEFT
|
||||||
|
| HTTOP
|
||||||
|
| HTTOPRIGHT
|
||||||
|
| HTBOTTOMRIGHT
|
||||||
|
| HTBOTTOM
|
||||||
|
| HTBOTTOMLEFT
|
||||||
|
) {
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dpi = unsafe { GetDpiForWindow(self.hwnd) };
|
||||||
|
let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
|
||||||
|
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
|
||||||
|
|
||||||
|
let mut cursor_point = POINT {
|
||||||
|
x: lparam.signed_loword().into(),
|
||||||
|
y: lparam.signed_hiword().into(),
|
||||||
|
};
|
||||||
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
|
if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
|
||||||
|
return LRESULT(HTTOP as _);
|
||||||
|
}
|
||||||
|
|
||||||
|
let titlebar_rect = self.get_titlebar_rect();
|
||||||
|
if let Ok(titlebar_rect) = titlebar_rect {
|
||||||
|
if cursor_point.y < titlebar_rect.bottom {
|
||||||
|
let caption_btn_width = unsafe { GetSystemMetricsForDpi(SM_CXSIZE, dpi) };
|
||||||
|
if cursor_point.x >= titlebar_rect.right - caption_btn_width {
|
||||||
|
return LRESULT(HTCLOSE as _);
|
||||||
|
} else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
|
||||||
|
return LRESULT(HTMAXBUTTON as _);
|
||||||
|
} else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
|
||||||
|
return LRESULT(HTMINBUTTON as _);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LRESULT(HTCAPTION as _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT(HTCLIENT as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_nc_mouse_move_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut cursor_point = POINT {
|
||||||
|
x: lparam.signed_loword().into(),
|
||||||
|
y: lparam.signed_hiword().into(),
|
||||||
|
};
|
||||||
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
|
let x = Pixels::from(cursor_point.x as f32);
|
||||||
|
let y = Pixels::from(cursor_point.y as f32);
|
||||||
|
self.mouse_position.set(Point { x, y });
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let event = MouseMoveEvent {
|
||||||
|
position: Point { x, y },
|
||||||
|
pressed_button: None,
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseMove(event)).default_prevented {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(callbacks);
|
||||||
|
unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_nc_mouse_down_msg(
|
||||||
|
&self,
|
||||||
|
button: MouseButton,
|
||||||
|
msg: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let mut cursor_point = POINT {
|
||||||
|
x: lparam.signed_loword().into(),
|
||||||
|
y: lparam.signed_hiword().into(),
|
||||||
|
};
|
||||||
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
|
let x = Pixels::from(cursor_point.x as f32);
|
||||||
|
let y = Pixels::from(cursor_point.y as f32);
|
||||||
|
let event = MouseDownEvent {
|
||||||
|
button: button.clone(),
|
||||||
|
position: Point { x, y },
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
click_count: 1,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(callbacks);
|
||||||
|
|
||||||
|
match wparam.0 as u32 {
|
||||||
|
// Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
|
||||||
|
HTMINBUTTON | HTMAXBUTTON | HTCLOSE => LRESULT(0),
|
||||||
|
_ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_nc_mouse_up_msg(
|
||||||
|
&self,
|
||||||
|
button: MouseButton,
|
||||||
|
msg: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let mut cursor_point = POINT {
|
||||||
|
x: lparam.signed_loword().into(),
|
||||||
|
y: lparam.signed_hiword().into(),
|
||||||
|
};
|
||||||
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
|
let x = Pixels::from(cursor_point.x as f32);
|
||||||
|
let y = Pixels::from(cursor_point.y as f32);
|
||||||
|
let event = MouseUpEvent {
|
||||||
|
button,
|
||||||
|
position: Point { x, y },
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
click_count: 1,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseUp(event)).default_prevented {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(callbacks);
|
||||||
|
|
||||||
|
if button == MouseButton::Left {
|
||||||
|
match wparam.0 as u32 {
|
||||||
|
HTMINBUTTON => unsafe {
|
||||||
|
ShowWindowAsync(self.hwnd, SW_MINIMIZE);
|
||||||
|
return LRESULT(0);
|
||||||
|
},
|
||||||
|
HTMAXBUTTON => unsafe {
|
||||||
|
if self.is_maximized() {
|
||||||
|
ShowWindowAsync(self.hwnd, SW_NORMAL);
|
||||||
|
} else {
|
||||||
|
ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
|
||||||
|
}
|
||||||
|
return LRESULT(0);
|
||||||
|
},
|
||||||
|
HTCLOSE => unsafe {
|
||||||
|
PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
|
||||||
|
.log_err();
|
||||||
|
return LRESULT(0);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Callbacks {
|
struct Callbacks {
|
||||||
request_frame: Option<Box<dyn FnMut()>>,
|
request_frame: Option<Box<dyn FnMut()>>,
|
||||||
input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
|
input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
|
||||||
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
active_status_change: Option<Box<dyn FnMut(bool)>>,
|
||||||
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||||
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
fullscreen: Option<Box<dyn FnMut(bool)>>,
|
||||||
@ -718,7 +1001,6 @@ impl WindowsWindow {
|
|||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
options: WindowParams,
|
options: WindowParams,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let dwexstyle = WINDOW_EX_STYLE::default();
|
|
||||||
let classname = register_wnd_class();
|
let classname = register_wnd_class();
|
||||||
let windowname = HSTRING::from(
|
let windowname = HSTRING::from(
|
||||||
options
|
options
|
||||||
@ -728,7 +1010,7 @@ impl WindowsWindow {
|
|||||||
.map(|title| title.as_ref())
|
.map(|title| title.as_ref())
|
||||||
.unwrap_or(""),
|
.unwrap_or(""),
|
||||||
);
|
);
|
||||||
let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE;
|
let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
|
||||||
let x = options.bounds.origin.x.0 as i32;
|
let x = options.bounds.origin.x.0 as i32;
|
||||||
let y = options.bounds.origin.y.0 as i32;
|
let y = options.bounds.origin.y.0 as i32;
|
||||||
let nwidth = options.bounds.size.width.0 as i32;
|
let nwidth = options.bounds.size.width.0 as i32;
|
||||||
@ -744,7 +1026,7 @@ impl WindowsWindow {
|
|||||||
let lpparam = Some(&context as *const _ as *const _);
|
let lpparam = Some(&context as *const _ as *const _);
|
||||||
unsafe {
|
unsafe {
|
||||||
CreateWindowExW(
|
CreateWindowExW(
|
||||||
dwexstyle,
|
WS_EX_APPWINDOW,
|
||||||
classname,
|
classname,
|
||||||
&windowname,
|
&windowname,
|
||||||
dwstyle,
|
dwstyle,
|
||||||
@ -785,7 +1067,7 @@ impl WindowsWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn maximize(&self) {
|
fn maximize(&self) {
|
||||||
unsafe { ShowWindow(self.inner.hwnd, SW_MAXIMIZE) };
|
unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,6 +1108,10 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_maximized(&self) -> bool {
|
||||||
|
self.inner.is_maximized()
|
||||||
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
let size = self.inner.size.get();
|
let size = self.inner.size.get();
|
||||||
@ -837,12 +1123,7 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn scale_factor(&self) -> f32 {
|
fn scale_factor(&self) -> f32 {
|
||||||
1.0
|
self.inner.scale_factor
|
||||||
}
|
|
||||||
|
|
||||||
// todo(windows)
|
|
||||||
fn titlebar_height(&self) -> Pixels {
|
|
||||||
20.0.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
@ -952,8 +1233,9 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
Some(done_rx)
|
Some(done_rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
fn activate(&self) {
|
||||||
fn activate(&self) {}
|
unsafe { ShowWindowAsync(self.inner.hwnd, SW_NORMAL) };
|
||||||
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn set_title(&mut self, title: &str) {
|
fn set_title(&mut self, title: &str) {
|
||||||
@ -963,16 +1245,18 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn set_edited(&mut self, edited: bool) {}
|
fn set_edited(&mut self, _edited: bool) {}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn show_character_palette(&self) {}
|
fn show_character_palette(&self) {}
|
||||||
|
|
||||||
// todo(windows)
|
fn minimize(&self) {
|
||||||
fn minimize(&self) {}
|
unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
|
||||||
|
}
|
||||||
|
|
||||||
// todo(windows)
|
fn zoom(&self) {
|
||||||
fn zoom(&self) {}
|
unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
|
||||||
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn toggle_fullscreen(&self) {}
|
fn toggle_fullscreen(&self) {}
|
||||||
@ -988,7 +1272,7 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
|
||||||
self.inner.callbacks.borrow_mut().input = Some(callback);
|
self.inner.callbacks.borrow_mut().input = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1028,7 +1312,7 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo(windows)
|
// todo(windows)
|
||||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
|
fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1041,11 +1325,16 @@ impl PlatformWindow for WindowsWindow {
|
|||||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||||
self.inner.renderer.borrow().sprite_atlas().clone()
|
self.inner.renderer.borrow().sprite_atlas().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_raw_handle(&self) -> HWND {
|
||||||
|
self.inner.hwnd
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[implement(IDropTarget)]
|
#[implement(IDropTarget)]
|
||||||
struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
|
struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
impl IDropTarget_Impl for WindowsDragDropHandler {
|
impl IDropTarget_Impl for WindowsDragDropHandler {
|
||||||
fn DragEnter(
|
fn DragEnter(
|
||||||
&self,
|
&self,
|
||||||
@ -1159,6 +1448,7 @@ fn register_wnd_class() -> PCWSTR {
|
|||||||
lpfnWndProc: Some(wnd_proc),
|
lpfnWndProc: Some(wnd_proc),
|
||||||
hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() },
|
hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() },
|
||||||
lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
|
lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
|
||||||
|
style: CS_HREDRAW | CS_VREDRAW,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
unsafe { RegisterClassW(&wc) };
|
unsafe { RegisterClassW(&wc) };
|
||||||
@ -1216,28 +1506,6 @@ pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
|
fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
|
||||||
match code {
|
match code {
|
||||||
// VK_0 - VK_9
|
// VK_0 - VK_9
|
||||||
|
@ -520,7 +520,7 @@ impl Window {
|
|||||||
handle
|
handle
|
||||||
.update(&mut cx, |_, cx| cx.dispatch_event(event))
|
.update(&mut cx, |_, cx| cx.dispatch_event(event))
|
||||||
.log_err()
|
.log_err()
|
||||||
.unwrap_or(false)
|
.unwrap_or(DispatchEventResult::default())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -574,6 +574,12 @@ impl Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub(crate) struct DispatchEventResult {
|
||||||
|
pub propagate: bool,
|
||||||
|
pub default_prevented: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
|
/// Indicates which region of the window is visible. Content falling outside of this mask will not be
|
||||||
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
|
/// rendered. Currently, only rectangular content masks are supported, but we give the mask its own type
|
||||||
/// to leave room to support more complex shapes in the future.
|
/// to leave room to support more complex shapes in the future.
|
||||||
@ -687,6 +693,12 @@ impl<'a> WindowContext<'a> {
|
|||||||
style
|
style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the platform window is maximized
|
||||||
|
/// On some platforms (namely Windows) this is different than the bounds being the size of the display
|
||||||
|
pub fn is_maximized(&self) -> bool {
|
||||||
|
self.window.platform_window.is_maximized()
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch the given action on the currently focused element.
|
/// Dispatch the given action on the currently focused element.
|
||||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||||
let focus_handle = self.focused();
|
let focus_handle = self.focused();
|
||||||
@ -1087,10 +1099,11 @@ impl<'a> WindowContext<'a> {
|
|||||||
/// You can create a keystroke with Keystroke::parse("").
|
/// You can create a keystroke with Keystroke::parse("").
|
||||||
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
|
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
|
||||||
let keystroke = keystroke.with_simulated_ime();
|
let keystroke = keystroke.with_simulated_ime();
|
||||||
if self.dispatch_event(PlatformInput::KeyDown(KeyDownEvent {
|
let result = self.dispatch_event(PlatformInput::KeyDown(KeyDownEvent {
|
||||||
keystroke: keystroke.clone(),
|
keystroke: keystroke.clone(),
|
||||||
is_held: false,
|
is_held: false,
|
||||||
})) {
|
}));
|
||||||
|
if !result.propagate {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1123,7 +1136,7 @@ impl<'a> WindowContext<'a> {
|
|||||||
|
|
||||||
/// Dispatch a mouse or keyboard event on the window.
|
/// Dispatch a mouse or keyboard event on the window.
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
|
pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult {
|
||||||
self.window.last_input_timestamp.set(Instant::now());
|
self.window.last_input_timestamp.set(Instant::now());
|
||||||
// Handlers may set this to false by calling `stop_propagation`.
|
// Handlers may set this to false by calling `stop_propagation`.
|
||||||
self.app.propagate_event = true;
|
self.app.propagate_event = true;
|
||||||
@ -1211,7 +1224,10 @@ impl<'a> WindowContext<'a> {
|
|||||||
self.dispatch_key_event(any_key_event);
|
self.dispatch_key_event(any_key_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
!self.app.propagate_event
|
DispatchEventResult {
|
||||||
|
propagate: self.app.propagate_event,
|
||||||
|
default_prevented: self.window.default_prevented,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
||||||
@ -1683,6 +1699,14 @@ impl<'a> WindowContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
impl WindowContext<'_> {
|
||||||
|
/// Returns the raw HWND handle for the window.
|
||||||
|
pub fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
|
||||||
|
self.window.platform_window.get_raw_handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Context for WindowContext<'_> {
|
impl Context for WindowContext<'_> {
|
||||||
type Result<T> = T;
|
type Result<T> = T;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ mod indicator;
|
|||||||
mod keybinding;
|
mod keybinding;
|
||||||
mod label;
|
mod label;
|
||||||
mod list;
|
mod list;
|
||||||
|
mod platform_titlebar;
|
||||||
mod popover;
|
mod popover;
|
||||||
mod popover_menu;
|
mod popover_menu;
|
||||||
mod right_click_menu;
|
mod right_click_menu;
|
||||||
@ -31,6 +32,7 @@ pub use indicator::*;
|
|||||||
pub use keybinding::*;
|
pub use keybinding::*;
|
||||||
pub use label::*;
|
pub use label::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
|
pub use platform_titlebar::*;
|
||||||
pub use popover::*;
|
pub use popover::*;
|
||||||
pub use popover_menu::*;
|
pub use popover_menu::*;
|
||||||
pub use right_click_menu::*;
|
pub use right_click_menu::*;
|
||||||
|
228
crates/ui/src/components/platform_titlebar.rs
Normal file
228
crates/ui/src/components/platform_titlebar.rs
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// allowing due to multiple platform conditional code
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div,
|
||||||
|
prelude::FluentBuilder,
|
||||||
|
px, transparent_black, AnyElement, Div, Element, ElementId, Fill, InteractiveElement,
|
||||||
|
Interactivity, IntoElement, ParentElement, Pixels, RenderOnce, Rgba, Stateful,
|
||||||
|
StatefulInteractiveElement, StyleRefinement, Styled,
|
||||||
|
WindowAppearance::{Dark, Light, VibrantDark, VibrantLight},
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::h_flex;
|
||||||
|
|
||||||
|
pub enum PlatformStyle {
|
||||||
|
Linux,
|
||||||
|
Windows,
|
||||||
|
MacOs,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
|
||||||
|
(1.75 * cx.rem_size()).max(px(32.))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformStyle {
|
||||||
|
pub fn platform() -> Self {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
Self::Windows
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
Self::MacOs
|
||||||
|
} else {
|
||||||
|
Self::Linux
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn windows(&self) -> bool {
|
||||||
|
matches!(self, Self::Windows)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn macos(&self) -> bool {
|
||||||
|
matches!(self, Self::MacOs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct PlatformTitlebar {
|
||||||
|
platform: PlatformStyle,
|
||||||
|
titlebar_bg: Fill,
|
||||||
|
content: Stateful<Div>,
|
||||||
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Styled for PlatformTitlebar {
|
||||||
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
|
self.content.style()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformTitlebar {
|
||||||
|
/// Change the platform style used
|
||||||
|
pub fn with_platform_style(self, style: PlatformStyle) -> Self {
|
||||||
|
Self {
|
||||||
|
platform: style,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn titlebar_top_padding(&self, cx: &WindowContext) -> Pixels {
|
||||||
|
if self.platform.windows() && cx.is_maximized() {
|
||||||
|
// todo(windows): get padding from win32 api, need HWND from window context somehow
|
||||||
|
// should be GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) * 2
|
||||||
|
px(8.0)
|
||||||
|
} else {
|
||||||
|
px(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn windows_caption_button_width(_cx: &WindowContext) -> Pixels {
|
||||||
|
// todo(windows): get padding from win32 api, need HWND from window context somehow
|
||||||
|
// should be GetSystemMetricsForDpi(SM_CXSIZE, dpi)
|
||||||
|
px(36.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_window_controls_right(&self, cx: &mut WindowContext) -> impl Element {
|
||||||
|
if self.platform.windows() {
|
||||||
|
let btn_height = titlebar_height(cx) - self.titlebar_top_padding(cx);
|
||||||
|
let close_btn_hover_color = Rgba {
|
||||||
|
r: 232.0 / 255.0,
|
||||||
|
g: 17.0 / 255.0,
|
||||||
|
b: 32.0 / 255.0,
|
||||||
|
a: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let btn_hover_color = match cx.appearance() {
|
||||||
|
Light | VibrantLight => Rgba {
|
||||||
|
r: 0.1,
|
||||||
|
g: 0.1,
|
||||||
|
b: 0.1,
|
||||||
|
a: 0.2,
|
||||||
|
},
|
||||||
|
Dark | VibrantDark => Rgba {
|
||||||
|
r: 0.9,
|
||||||
|
g: 0.9,
|
||||||
|
b: 0.9,
|
||||||
|
a: 0.1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn windows_caption_btn(
|
||||||
|
id: &'static str,
|
||||||
|
icon_text: &'static str,
|
||||||
|
hover_color: Rgba,
|
||||||
|
cx: &WindowContext,
|
||||||
|
) -> Stateful<Div> {
|
||||||
|
let mut active_color = hover_color;
|
||||||
|
active_color.a *= 0.2;
|
||||||
|
h_flex()
|
||||||
|
.id(id)
|
||||||
|
.h_full()
|
||||||
|
.justify_center()
|
||||||
|
.content_center()
|
||||||
|
.items_center()
|
||||||
|
.w(PlatformTitlebar::windows_caption_button_width(cx))
|
||||||
|
.hover(|style| style.bg(hover_color))
|
||||||
|
.active(|style| style.bg(active_color))
|
||||||
|
.child(icon_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id("caption-buttons-windows")
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.justify_center()
|
||||||
|
.content_stretch()
|
||||||
|
.max_h(btn_height)
|
||||||
|
.min_h(btn_height)
|
||||||
|
.font("Segoe Fluent Icons")
|
||||||
|
.text_size(px(10.0))
|
||||||
|
.children(vec![
|
||||||
|
windows_caption_btn("minimize", "\u{e921}", btn_hover_color, cx), // minimize icon
|
||||||
|
windows_caption_btn(
|
||||||
|
"maximize",
|
||||||
|
if cx.is_maximized() {
|
||||||
|
"\u{e923}" // restore icon
|
||||||
|
} else {
|
||||||
|
"\u{e922}" // maximize icon
|
||||||
|
},
|
||||||
|
btn_hover_color,
|
||||||
|
cx,
|
||||||
|
),
|
||||||
|
windows_caption_btn("close", "\u{e8bb}", close_btn_hover_color, cx), // close icon
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
div().id("caption-buttons-windows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the background color of titlebar.
|
||||||
|
pub fn titlebar_bg<F>(mut self, fill: F) -> Self
|
||||||
|
where
|
||||||
|
F: Into<Fill>,
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.titlebar_bg = fill.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn platform_titlebar(id: impl Into<ElementId>) -> PlatformTitlebar {
|
||||||
|
let id = id.into();
|
||||||
|
PlatformTitlebar {
|
||||||
|
platform: PlatformStyle::platform(),
|
||||||
|
titlebar_bg: transparent_black().into(),
|
||||||
|
content: div().id(id.clone()),
|
||||||
|
children: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for PlatformTitlebar {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
let titlebar_height = titlebar_height(cx);
|
||||||
|
let titlebar_top_padding = self.titlebar_top_padding(cx);
|
||||||
|
let window_controls_right = self.render_window_controls_right(cx);
|
||||||
|
let macos = self.platform.macos();
|
||||||
|
h_flex()
|
||||||
|
.id("titlebar")
|
||||||
|
.w_full()
|
||||||
|
.pt(titlebar_top_padding)
|
||||||
|
.h(titlebar_height)
|
||||||
|
.map(|this| {
|
||||||
|
if cx.is_fullscreen() {
|
||||||
|
this.pl_2()
|
||||||
|
} else if macos {
|
||||||
|
// Use pixels here instead of a rem-based size because the macOS traffic
|
||||||
|
// lights are a static size, and don't scale with the rest of the UI.
|
||||||
|
this.pl(px(80.))
|
||||||
|
} else {
|
||||||
|
this.pl_2()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.bg(self.titlebar_bg)
|
||||||
|
.content_stretch()
|
||||||
|
.child(
|
||||||
|
self.content
|
||||||
|
.flex()
|
||||||
|
.flex_row()
|
||||||
|
.w_full()
|
||||||
|
.id("titlebar-content")
|
||||||
|
.children(self.children),
|
||||||
|
)
|
||||||
|
.child(window_controls_right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InteractiveElement for PlatformTitlebar {
|
||||||
|
fn interactivity(&mut self) -> &mut Interactivity {
|
||||||
|
self.content.interactivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl StatefulInteractiveElement for PlatformTitlebar {}
|
||||||
|
|
||||||
|
impl ParentElement for PlatformTitlebar {
|
||||||
|
fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
|
||||||
|
self.children.extend(elements)
|
||||||
|
}
|
||||||
|
}
|
@ -26,13 +26,11 @@ use futures::{
|
|||||||
Future, FutureExt, StreamExt,
|
Future, FutureExt, StreamExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
|
actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
|
||||||
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, DragMoveEvent, Element,
|
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId,
|
||||||
ElementContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, Global,
|
EventEmitter, FocusHandle, FocusableView, Global, GlobalPixels, KeyContext, Keystroke,
|
||||||
GlobalPixels, InteractiveElement, IntoElement, KeyContext, Keystroke, LayoutId, ManagedView,
|
LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
|
||||||
Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, Render,
|
Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
|
||||||
SharedString, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
|
||||||
WindowContext, WindowHandle, WindowOptions,
|
|
||||||
};
|
};
|
||||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -72,7 +70,11 @@ use task::SpawnInTerminal;
|
|||||||
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||||
pub use ui;
|
pub use ui;
|
||||||
use ui::{px, Label};
|
use ui::{
|
||||||
|
div, Context as _, Div, Element, ElementContext, InteractiveElement as _, IntoElement, Label,
|
||||||
|
ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
|
||||||
|
WindowContext,
|
||||||
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
|
pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
|
||||||
@ -418,6 +420,7 @@ impl AppState {
|
|||||||
pub fn test(cx: &mut AppContext) -> Arc<Self> {
|
pub fn test(cx: &mut AppContext) -> Arc<Self> {
|
||||||
use node_runtime::FakeNodeRuntime;
|
use node_runtime::FakeNodeRuntime;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
|
use ui::Context as _;
|
||||||
|
|
||||||
if !cx.has_global::<SettingsStore>() {
|
if !cx.has_global::<SettingsStore>() {
|
||||||
let settings_store = SettingsStore::test(cx);
|
let settings_store = SettingsStore::test(cx);
|
||||||
@ -4793,10 +4796,6 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
|
|||||||
Some(size((width as f64).into(), (height as f64).into()))
|
Some(size((width as f64).into(), (height as f64).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
|
|
||||||
(1.75 * cx.rem_size()).max(px(32.))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DisconnectedOverlay;
|
struct DisconnectedOverlay;
|
||||||
|
|
||||||
impl Element for DisconnectedOverlay {
|
impl Element for DisconnectedOverlay {
|
||||||
@ -4810,7 +4809,7 @@ impl Element for DisconnectedOverlay {
|
|||||||
.bg(background)
|
.bg(background)
|
||||||
.absolute()
|
.absolute()
|
||||||
.left_0()
|
.left_0()
|
||||||
.top(titlebar_height(cx))
|
.top(ui::titlebar_height(cx))
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
@ -4866,7 +4865,10 @@ mod tests {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use fs::FakeFs;
|
use fs::FakeFs;
|
||||||
use gpui::{px, DismissEvent, Empty, TestAppContext, VisualTestContext};
|
use gpui::{
|
||||||
|
px, DismissEvent, Empty, EventEmitter, FocusHandle, FocusableView, Render, TestAppContext,
|
||||||
|
VisualTestContext,
|
||||||
|
};
|
||||||
use project::{Project, ProjectEntryId};
|
use project::{Project, ProjectEntryId};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@ -5843,6 +5845,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod register_project_item_tests {
|
mod register_project_item_tests {
|
||||||
|
use ui::Context as _;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const TEST_PNG_KIND: &str = "TestPngItemView";
|
const TEST_PNG_KIND: &str = "TestPngItemView";
|
||||||
|
Loading…
Reference in New Issue
Block a user