collab ui: Fix notification windows on external monitors (#9817)
Sharing a project displays a notification (window) on every screen. Previously there was an issue with the positioning of windows on all screens except the primary screen. As you can see here:  Now:  @mikayla-maki and I also decided to refactor the `WindowOptions` a bit. Previously you could specify bounds which controlled the positioning and size of the window in the global coordinate space, while also providing a display id (which screen to show the window on). This can lead to unusual behavior because you could theoretically specify a global bound which does not even belong to the display id which was provided. Therefore we changed the api to this: ```rust struct WindowOptions { /// The bounds of the window in screen coordinates /// None -> inherit, Some(bounds) -> set bounds. pub bounds: Option<Bounds<DevicePixels>>, /// The display to create the window on, if this is None, /// the window will be created on the main display pub display_id: Option<DisplayId>, } ``` This lets you specify a display id, which maps to the screen where the window should be created and bounds relative to the upper left of the screen. Release Notes: - Fixed positioning of popup windows (e.g. when sharing a project) when using multiple external displays. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
ffd698be14
commit
e272acd1bc
25 changed files with 331 additions and 352 deletions
|
@ -13,7 +13,7 @@ use call::{report_call_event_for_room, ActiveCall};
|
||||||
pub use collab_panel::CollabPanel;
|
pub use collab_panel::CollabPanel;
|
||||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
|
actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
|
||||||
WindowKind, WindowOptions,
|
WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
use panel_settings::MessageEditorSettings;
|
use panel_settings::MessageEditorSettings;
|
||||||
|
@ -97,13 +97,13 @@ fn notification_window_options(
|
||||||
screen: Rc<dyn PlatformDisplay>,
|
screen: Rc<dyn PlatformDisplay>,
|
||||||
window_size: Size<Pixels>,
|
window_size: Size<Pixels>,
|
||||||
) -> WindowOptions {
|
) -> WindowOptions {
|
||||||
let notification_margin_width = GlobalPixels::from(16.);
|
let notification_margin_width = DevicePixels::from(16);
|
||||||
let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
|
let notification_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
|
||||||
|
|
||||||
let screen_bounds = screen.bounds();
|
let screen_bounds = screen.bounds();
|
||||||
let size: Size<GlobalPixels> = window_size.into();
|
let size: Size<DevicePixels> = window_size.into();
|
||||||
|
|
||||||
let bounds = gpui::Bounds::<GlobalPixels> {
|
let bounds = gpui::Bounds::<DevicePixels> {
|
||||||
origin: screen_bounds.upper_right()
|
origin: screen_bounds.upper_right()
|
||||||
- point(
|
- point(
|
||||||
size.width + notification_margin_width,
|
size.width + notification_margin_width,
|
||||||
|
|
|
@ -7304,8 +7304,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(Bounds::from_corners(
|
bounds: Some(Bounds::from_corners(
|
||||||
gpui::Point::new(0_f64.into(), 0_f64.into()),
|
gpui::Point::new(0.into(), 0.into()),
|
||||||
gpui::Point::new(10_f64.into(), 80_f64.into()),
|
gpui::Point::new(10.into(), 80.into()),
|
||||||
)),
|
)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ fn main() {
|
||||||
.with_assets(Assets {})
|
.with_assets(Assets {})
|
||||||
.run(|cx: &mut AppContext| {
|
.run(|cx: &mut AppContext| {
|
||||||
let options = WindowOptions {
|
let options = WindowOptions {
|
||||||
bounds: Some(Bounds::centered(size(px(300.), px(300.)), cx)),
|
bounds: Some(Bounds::centered(None, size(px(300.), px(300.)), cx)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
cx.open_window(options, |cx| {
|
cx.open_window(options, |cx| {
|
||||||
|
|
|
@ -23,7 +23,7 @@ impl Render for HelloWorld {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new().run(|cx: &mut AppContext| {
|
App::new().run(|cx: &mut AppContext| {
|
||||||
let bounds = Bounds::centered(size(px(600.0), px(600.0)), cx);
|
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
|
|
66
crates/gpui/examples/window_positioning.rs
Normal file
66
crates/gpui/examples/window_positioning.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use gpui::*;
|
||||||
|
|
||||||
|
struct WindowContent {
|
||||||
|
text: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for WindowContent {
|
||||||
|
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.bg(rgb(0x1e2025))
|
||||||
|
.size_full()
|
||||||
|
.justify_center()
|
||||||
|
.items_center()
|
||||||
|
.text_xl()
|
||||||
|
.text_color(rgb(0xffffff))
|
||||||
|
.child(self.text.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new().run(|cx: &mut AppContext| {
|
||||||
|
// Create several new windows, positioned in the top right corner of each screen
|
||||||
|
|
||||||
|
for screen in cx.displays() {
|
||||||
|
let options = {
|
||||||
|
let popup_margin_width = DevicePixels::from(16);
|
||||||
|
let popup_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
|
||||||
|
|
||||||
|
let window_size = Size {
|
||||||
|
width: px(400.),
|
||||||
|
height: px(72.),
|
||||||
|
};
|
||||||
|
|
||||||
|
let screen_bounds = screen.bounds();
|
||||||
|
let size: Size<DevicePixels> = window_size.into();
|
||||||
|
|
||||||
|
let bounds = gpui::Bounds::<DevicePixels> {
|
||||||
|
origin: screen_bounds.upper_right()
|
||||||
|
- point(size.width + popup_margin_width, popup_margin_height),
|
||||||
|
size: window_size.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowOptions {
|
||||||
|
// Set the bounds of the window in screen coordinates
|
||||||
|
bounds: Some(bounds),
|
||||||
|
// Specify the display_id to ensure the window is created on the correct screen
|
||||||
|
display_id: Some(screen.id()),
|
||||||
|
|
||||||
|
titlebar: None,
|
||||||
|
focus: false,
|
||||||
|
show: true,
|
||||||
|
kind: WindowKind::PopUp,
|
||||||
|
is_movable: false,
|
||||||
|
fullscreen: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.open_window(options, |cx| {
|
||||||
|
cx.new_view(|_| WindowContent {
|
||||||
|
text: format!("{:?}", screen.id()).into(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -30,11 +30,11 @@ use util::{
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
|
||||||
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
|
||||||
DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
|
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
|
||||||
LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder,
|
Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
|
||||||
PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString, SubscriberSet,
|
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString,
|
||||||
Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
|
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window,
|
||||||
WindowContext, WindowHandle, WindowId,
|
WindowAppearance, WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod async_context;
|
mod async_context;
|
||||||
|
@ -525,6 +525,14 @@ impl AppContext {
|
||||||
self.platform.primary_display()
|
self.platform.primary_display()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the display with the given ID, if one exists.
|
||||||
|
pub fn find_display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||||
|
self.displays()
|
||||||
|
.iter()
|
||||||
|
.find(|display| display.id() == id)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the appearance of the application's windows.
|
/// Returns the appearance of the application's windows.
|
||||||
pub fn window_appearance(&self) -> WindowAppearance {
|
pub fn window_appearance(&self) -> WindowAppearance {
|
||||||
self.platform.window_appearance()
|
self.platform.window_appearance()
|
||||||
|
|
|
@ -173,7 +173,7 @@ impl TestAppContext {
|
||||||
let mut cx = self.app.borrow_mut();
|
let mut cx = self.app.borrow_mut();
|
||||||
|
|
||||||
// Some tests rely on the window size matching the bounds of the test display
|
// Some tests rely on the window size matching the bounds of the test display
|
||||||
let bounds = Bounds::maximized(&mut cx);
|
let bounds = Bounds::maximized(None, &mut cx);
|
||||||
cx.open_window(
|
cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
|
@ -186,7 +186,7 @@ impl TestAppContext {
|
||||||
/// Adds a new window with no content.
|
/// Adds a new window with no content.
|
||||||
pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
|
pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
|
||||||
let mut cx = self.app.borrow_mut();
|
let mut cx = self.app.borrow_mut();
|
||||||
let bounds = Bounds::maximized(&mut cx);
|
let bounds = Bounds::maximized(None, &mut cx);
|
||||||
let window = cx.open_window(
|
let window = cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
|
@ -209,7 +209,7 @@ impl TestAppContext {
|
||||||
V: 'static + Render,
|
V: 'static + Render,
|
||||||
{
|
{
|
||||||
let mut cx = self.app.borrow_mut();
|
let mut cx = self.app.borrow_mut();
|
||||||
let bounds = Bounds::maximized(&mut cx);
|
let bounds = Bounds::maximized(None, &mut cx);
|
||||||
let window = cx.open_window(
|
let window = cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::{
|
||||||
ops::{Add, Div, Mul, MulAssign, Sub},
|
ops::{Add, Div, Mul, MulAssign, Sub},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::AppContext;
|
use crate::{AppContext, DisplayId};
|
||||||
|
|
||||||
/// An axis along which a measurement can be made.
|
/// An axis along which a measurement can be made.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
@ -363,11 +363,11 @@ pub struct Size<T: Clone + Default + Debug> {
|
||||||
pub height: T,
|
pub height: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Size<GlobalPixels>> for Size<Pixels> {
|
impl From<Size<DevicePixels>> for Size<Pixels> {
|
||||||
fn from(size: Size<GlobalPixels>) -> Self {
|
fn from(size: Size<DevicePixels>) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: Pixels(size.width.0),
|
width: Pixels(size.width.0 as f32),
|
||||||
height: Pixels(size.height.0),
|
height: Pixels(size.height.0 as f32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -604,11 +604,11 @@ impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Size<Pixels>> for Size<GlobalPixels> {
|
impl From<Size<Pixels>> for Size<DevicePixels> {
|
||||||
fn from(size: Size<Pixels>) -> Self {
|
fn from(size: Size<Pixels>) -> Self {
|
||||||
Size {
|
Size {
|
||||||
width: GlobalPixels(size.width.0),
|
width: DevicePixels(size.width.0 as i32),
|
||||||
height: GlobalPixels(size.height.0),
|
height: DevicePixels(size.height.0 as i32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -693,31 +693,43 @@ pub struct Bounds<T: Clone + Default + Debug> {
|
||||||
pub size: Size<T>,
|
pub size: Size<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bounds<GlobalPixels> {
|
impl Bounds<DevicePixels> {
|
||||||
/// Generate a centered bounds for the primary display
|
/// Generate a centered bounds for the given display or primary display if none is provided
|
||||||
pub fn centered(size: impl Into<Size<GlobalPixels>>, cx: &mut AppContext) -> Self {
|
pub fn centered(
|
||||||
|
display_id: Option<DisplayId>,
|
||||||
|
size: impl Into<Size<DevicePixels>>,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) -> Self {
|
||||||
|
let display = display_id
|
||||||
|
.and_then(|id| cx.find_display(id))
|
||||||
|
.or_else(|| cx.primary_display());
|
||||||
|
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
cx.primary_display()
|
display
|
||||||
.map(|display| {
|
.map(|display| {
|
||||||
let center = display.bounds().center();
|
let center = display.bounds().center();
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: point(center.x - size.width / 2.0, center.y - size.height / 2.0),
|
origin: point(center.x - size.width / 2, center.y - size.height / 2),
|
||||||
size,
|
size,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| Bounds {
|
.unwrap_or_else(|| Bounds {
|
||||||
origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
|
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||||
size,
|
size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate maximized bounds for the primary display
|
/// Generate maximized bounds for the given display or primary display if none is provided
|
||||||
pub fn maximized(cx: &mut AppContext) -> Self {
|
pub fn maximized(display_id: Option<DisplayId>, cx: &mut AppContext) -> Self {
|
||||||
cx.primary_display()
|
let display = display_id
|
||||||
|
.and_then(|id| cx.find_display(id))
|
||||||
|
.or_else(|| cx.primary_display());
|
||||||
|
|
||||||
|
display
|
||||||
.map(|display| display.bounds())
|
.map(|display| display.bounds())
|
||||||
.unwrap_or_else(|| Bounds {
|
.unwrap_or_else(|| Bounds {
|
||||||
origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
|
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||||
size: size(GlobalPixels(1024.0), GlobalPixels(768.0)),
|
size: size(DevicePixels(1024), DevicePixels(768)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2455,34 +2467,6 @@ impl From<ScaledPixels> for f64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents pixels in a global coordinate space, which can span across multiple displays.
|
|
||||||
///
|
|
||||||
/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
|
|
||||||
/// display's boundaries. This type is particularly useful in multi-monitor setups where
|
|
||||||
/// positioning and measurements need to be consistent and relative to a "global" origin point
|
|
||||||
/// rather than being relative to any individual display.
|
|
||||||
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct GlobalPixels(pub(crate) f32);
|
|
||||||
|
|
||||||
impl Debug for GlobalPixels {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{} px (global coordinate space)", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GlobalPixels> for f64 {
|
|
||||||
fn from(global_pixels: GlobalPixels) -> Self {
|
|
||||||
global_pixels.0 as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<f64> for GlobalPixels {
|
|
||||||
fn from(global_pixels: f64) -> Self {
|
|
||||||
GlobalPixels(global_pixels as f32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [`WindowContext::set_rem_size`][set_rem_size].
|
/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [`WindowContext::set_rem_size`][set_rem_size].
|
||||||
///
|
///
|
||||||
/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
|
/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
|
||||||
|
@ -2834,12 +2818,6 @@ impl Half for Rems {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Half for GlobalPixels {
|
|
||||||
fn half(&self) -> Self {
|
|
||||||
Self(self.0 / 2.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides a trait for types that can negate their values.
|
/// Provides a trait for types that can negate their values.
|
||||||
pub trait Negate {
|
pub trait Negate {
|
||||||
/// Returns the negation of the given value
|
/// Returns the negation of the given value
|
||||||
|
@ -2882,12 +2860,6 @@ impl Negate for Rems {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Negate for GlobalPixels {
|
|
||||||
fn negate(self) -> Self {
|
|
||||||
Self(-self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait for checking if a value is zero.
|
/// A trait for checking if a value is zero.
|
||||||
///
|
///
|
||||||
/// This trait provides a method to determine if a value is considered to be zero.
|
/// This trait provides a method to determine if a value is considered to be zero.
|
||||||
|
|
|
@ -23,9 +23,9 @@ mod windows;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
|
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
|
||||||
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels,
|
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, Keymap,
|
||||||
GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams,
|
LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams,
|
||||||
RenderImageParams, RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -152,7 +152,7 @@ pub trait PlatformDisplay: Send + Sync + Debug {
|
||||||
fn uuid(&self) -> Result<Uuid>;
|
fn uuid(&self) -> Result<Uuid>;
|
||||||
|
|
||||||
/// Get the bounds for this display
|
/// Get the bounds for this display
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels>;
|
fn bounds(&self) -> Bounds<DevicePixels>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An opaque identifier for a hardware display
|
/// An opaque identifier for a hardware display
|
||||||
|
@ -168,7 +168,7 @@ impl Debug for DisplayId {
|
||||||
unsafe impl Send for DisplayId {}
|
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<DevicePixels>;
|
||||||
fn is_maximized(&self) -> bool;
|
fn is_maximized(&self) -> bool;
|
||||||
fn is_minimized(&self) -> bool;
|
fn is_minimized(&self) -> bool;
|
||||||
fn content_size(&self) -> Size<Pixels>;
|
fn content_size(&self) -> Size<Pixels>;
|
||||||
|
@ -508,8 +508,9 @@ pub trait InputHandler: 'static {
|
||||||
/// The variables that can be configured when creating a new window
|
/// The variables that can be configured when creating a new window
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
|
/// The bounds of the window in screen coordinates.
|
||||||
/// None -> inherit, Some(bounds) -> set bounds
|
/// None -> inherit, Some(bounds) -> set bounds
|
||||||
pub bounds: Option<Bounds<GlobalPixels>>,
|
pub bounds: Option<Bounds<DevicePixels>>,
|
||||||
|
|
||||||
/// The titlebar configuration of the window
|
/// The titlebar configuration of the window
|
||||||
pub titlebar: Option<TitlebarOptions>,
|
pub titlebar: Option<TitlebarOptions>,
|
||||||
|
@ -529,7 +530,8 @@ pub struct WindowOptions {
|
||||||
/// Whether the window should be movable by the user
|
/// Whether the window should be movable by the user
|
||||||
pub is_movable: bool,
|
pub is_movable: bool,
|
||||||
|
|
||||||
/// The display to create the window on
|
/// The display to create the window on, if this is None,
|
||||||
|
/// the window will be created on the main display
|
||||||
pub display_id: Option<DisplayId>,
|
pub display_id: Option<DisplayId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +539,7 @@ pub struct WindowOptions {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct WindowParams {
|
pub(crate) struct WindowParams {
|
||||||
///
|
///
|
||||||
pub bounds: Bounds<GlobalPixels>,
|
pub bounds: Bounds<DevicePixels>,
|
||||||
|
|
||||||
/// The titlebar configuration of the window
|
/// The titlebar configuration of the window
|
||||||
pub titlebar: Option<TitlebarOptions>,
|
pub titlebar: Option<TitlebarOptions>,
|
||||||
|
@ -552,7 +554,6 @@ pub(crate) struct WindowParams {
|
||||||
|
|
||||||
pub show: bool,
|
pub show: bool,
|
||||||
|
|
||||||
/// The display to create the window on
|
|
||||||
pub display_id: Option<DisplayId>,
|
pub display_id: Option<DisplayId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,10 +600,6 @@ pub enum WindowKind {
|
||||||
PopUp,
|
PopUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Platform level interface
|
|
||||||
/// bounds: Bounds<GlobalPixels>
|
|
||||||
/// fullscreen: bool
|
|
||||||
|
|
||||||
/// The appearance of the window, as defined by the operating system.
|
/// The appearance of the window, as defined by the operating system.
|
||||||
///
|
///
|
||||||
/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
|
/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct WaylandDisplay {}
|
pub(crate) struct WaylandDisplay {}
|
||||||
|
@ -19,12 +19,12 @@ impl PlatformDisplay for WaylandDisplay {
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(linux)
|
// todo(linux)
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: Default::default(),
|
origin: Default::default(),
|
||||||
size: Size {
|
size: Size {
|
||||||
width: GlobalPixels(1000f32),
|
width: DevicePixels(1000),
|
||||||
height: GlobalPixels(500f32),
|
height: DevicePixels(500),
|
||||||
},
|
},
|
||||||
} // return some fake data so it doesn't panic
|
} // return some fake data so it doesn't panic
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use crate::platform::linux::wayland::display::WaylandDisplay;
|
||||||
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
|
px, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||||
PromptLevel, Size, WindowAppearance, WindowParams,
|
PromptLevel, Size, WindowAppearance, WindowParams,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ impl HasDisplayHandle for WaylandWindow {
|
||||||
|
|
||||||
impl PlatformWindow for WaylandWindow {
|
impl PlatformWindow for WaylandWindow {
|
||||||
// todo(linux)
|
// todo(linux)
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@ use anyhow::Result;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
|
use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
|
||||||
|
|
||||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct X11Display {
|
pub(crate) struct X11Display {
|
||||||
x_screen_index: usize,
|
x_screen_index: usize,
|
||||||
bounds: Bounds<GlobalPixels>,
|
bounds: Bounds<DevicePixels>,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ impl X11Display {
|
||||||
bounds: Bounds {
|
bounds: Bounds {
|
||||||
origin: Default::default(),
|
origin: Default::default(),
|
||||||
size: Size {
|
size: Size {
|
||||||
width: GlobalPixels(screen.width_in_pixels as f32),
|
width: DevicePixels(screen.width_in_pixels as i32),
|
||||||
height: GlobalPixels(screen.height_in_pixels as f32),
|
height: DevicePixels(screen.height_in_pixels as i32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
uuid: Uuid::from_bytes([0; 16]),
|
uuid: Uuid::from_bytes([0; 16]),
|
||||||
|
@ -37,7 +37,7 @@ impl PlatformDisplay for X11Display {
|
||||||
Ok(self.uuid)
|
Ok(self.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.bounds
|
self.bounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
|
platform::blade::BladeRenderer, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformAtlas,
|
||||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
|
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
|
||||||
Scene, Size, WindowAppearance, WindowOptions, WindowParams,
|
Scene, Size, WindowAppearance, WindowOptions, WindowParams,
|
||||||
};
|
};
|
||||||
|
@ -245,7 +245,7 @@ impl X11WindowState {
|
||||||
x_window,
|
x_window,
|
||||||
callbacks: RefCell::new(Callbacks::default()),
|
callbacks: RefCell::new(Callbacks::default()),
|
||||||
inner: RefCell::new(LinuxWindowInner {
|
inner: RefCell::new(LinuxWindowInner {
|
||||||
bounds: params.bounds.map(|v| v.0 as i32),
|
bounds: params.bounds.map(|v| v.0),
|
||||||
scale_factor: 1.0,
|
scale_factor: 1.0,
|
||||||
renderer: BladeRenderer::new(gpu, gpu_extent),
|
renderer: BladeRenderer::new(gpu, gpu_extent),
|
||||||
input_handler: None,
|
input_handler: None,
|
||||||
|
@ -325,12 +325,8 @@ impl X11WindowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformWindow for X11Window {
|
impl PlatformWindow for X11Window {
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.0
|
self.0.inner.borrow_mut().bounds.map(|v| v.into())
|
||||||
.inner
|
|
||||||
.borrow_mut()
|
|
||||||
.bounds
|
|
||||||
.map(|v| GlobalPixels(v as f32))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo(linux)
|
// todo(linux)
|
||||||
|
|
|
@ -22,7 +22,7 @@ mod text_system;
|
||||||
mod window;
|
mod window;
|
||||||
mod window_appearance;
|
mod window_appearance;
|
||||||
|
|
||||||
use crate::{px, size, GlobalPixels, Pixels, Size};
|
use crate::{px, size, DevicePixels, Pixels, Size};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
|
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
|
||||||
|
@ -122,9 +122,9 @@ impl From<NSRect> for Size<Pixels> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NSRect> for Size<GlobalPixels> {
|
impl From<NSRect> for Size<DevicePixels> {
|
||||||
fn from(rect: NSRect) -> Self {
|
fn from(rect: NSRect) -> Self {
|
||||||
let NSSize { width, height } = rect.size;
|
let NSSize { width, height } = rect.size;
|
||||||
size(width.into(), height.into())
|
size(DevicePixels(width as i32), DevicePixels(height as i32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
|
use crate::{point, size, Bounds, DevicePixels, DisplayId, PlatformDisplay};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::NSScreen,
|
appkit::NSScreen,
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
foundation::{NSDictionary, NSString},
|
||||||
};
|
};
|
||||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
||||||
|
@ -69,49 +69,6 @@ extern "C" {
|
||||||
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the given rectangle from Cocoa's coordinate space to GPUI's coordinate space.
|
|
||||||
///
|
|
||||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
|
||||||
/// with the Y axis pointing upwards.
|
|
||||||
///
|
|
||||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
|
||||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
|
||||||
pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
|
|
||||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
|
||||||
|
|
||||||
Bounds {
|
|
||||||
origin: point(
|
|
||||||
GlobalPixels(rect.origin.x as f32),
|
|
||||||
GlobalPixels(
|
|
||||||
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
size: size(
|
|
||||||
GlobalPixels(rect.size.width as f32),
|
|
||||||
GlobalPixels(rect.size.height as f32),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the given rectangle from GPUI's coordinate system to Cocoa's native coordinate space.
|
|
||||||
///
|
|
||||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
|
||||||
/// with the Y axis pointing upwards.
|
|
||||||
///
|
|
||||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
|
||||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
|
||||||
pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
|
|
||||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
|
||||||
|
|
||||||
NSRect::new(
|
|
||||||
NSPoint::new(
|
|
||||||
bounds.origin.x.into(),
|
|
||||||
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
|
|
||||||
),
|
|
||||||
NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlatformDisplay for MacDisplay {
|
impl PlatformDisplay for MacDisplay {
|
||||||
fn id(&self) -> DisplayId {
|
fn id(&self) -> DisplayId {
|
||||||
DisplayId(self.0)
|
DisplayId(self.0)
|
||||||
|
@ -145,20 +102,17 @@ impl PlatformDisplay for MacDisplay {
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
unsafe {
|
unsafe {
|
||||||
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||||
// the top left of the primary display.
|
// the top left of the primary display.
|
||||||
let bounds = CGDisplayBounds(self.0);
|
let bounds = CGDisplayBounds(self.0);
|
||||||
|
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: point(
|
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||||
GlobalPixels(bounds.origin.x as f32),
|
|
||||||
GlobalPixels(bounds.origin.y as f32),
|
|
||||||
),
|
|
||||||
size: size(
|
size: size(
|
||||||
GlobalPixels(bounds.size.width as f32),
|
DevicePixels(bounds.size.width as i32),
|
||||||
GlobalPixels(bounds.size.height as f32),
|
DevicePixels(bounds.size.height as i32),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{global_bounds_from_ns_rect, ns_string, renderer, MacDisplay, NSRange};
|
use super::{ns_string, renderer, MacDisplay, NSRange};
|
||||||
use crate::{
|
use crate::{
|
||||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
platform::PlatformInputHandler, point, px, size, AnyWindowHandle, Bounds, DevicePixels,
|
||||||
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke,
|
||||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel,
|
||||||
PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowKind, WindowParams,
|
Size, Timer, WindowAppearance, WindowKind, WindowParams,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -441,9 +441,28 @@ impl MacWindowState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
let frame = unsafe { NSWindow::frame(self.native_window) };
|
let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
|
||||||
global_bounds_from_ns_rect(frame)
|
let screen_frame = unsafe {
|
||||||
|
let screen = NSWindow::screen(self.native_window);
|
||||||
|
NSScreen::frame(screen)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flip the y coordinate to be top-left origin
|
||||||
|
window_frame.origin.y =
|
||||||
|
screen_frame.size.height - window_frame.origin.y - window_frame.size.height;
|
||||||
|
|
||||||
|
let bounds = Bounds::new(
|
||||||
|
point(
|
||||||
|
((window_frame.origin.x - screen_frame.origin.x) as i32).into(),
|
||||||
|
((window_frame.origin.y - screen_frame.origin.y) as i32).into(),
|
||||||
|
),
|
||||||
|
size(
|
||||||
|
(window_frame.size.width as i32).into(),
|
||||||
|
(window_frame.size.height as i32).into(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
|
@ -494,9 +513,9 @@ impl MacWindow {
|
||||||
titlebar,
|
titlebar,
|
||||||
kind,
|
kind,
|
||||||
is_movable,
|
is_movable,
|
||||||
display_id,
|
|
||||||
focus,
|
focus,
|
||||||
show,
|
show,
|
||||||
|
display_id,
|
||||||
}: WindowParams,
|
}: WindowParams,
|
||||||
executor: ForegroundExecutor,
|
executor: ForegroundExecutor,
|
||||||
renderer_context: renderer::Context,
|
renderer_context: renderer::Context,
|
||||||
|
@ -529,28 +548,37 @@ impl MacWindow {
|
||||||
|
|
||||||
let display = display_id
|
let display = display_id
|
||||||
.and_then(MacDisplay::find_by_id)
|
.and_then(MacDisplay::find_by_id)
|
||||||
.unwrap_or_else(MacDisplay::primary);
|
.unwrap_or_else(|| MacDisplay::primary());
|
||||||
|
|
||||||
let mut target_screen = nil;
|
let mut target_screen = nil;
|
||||||
|
let mut screen_frame = None;
|
||||||
|
|
||||||
let screens = NSScreen::screens(nil);
|
let screens = NSScreen::screens(nil);
|
||||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||||
|
let frame = NSScreen::visibleFrame(screen);
|
||||||
let display_id = display_id_for_screen(screen);
|
let display_id = display_id_for_screen(screen);
|
||||||
if display_id == display.id().0 {
|
if display_id == display.0 {
|
||||||
|
screen_frame = Some(frame);
|
||||||
target_screen = screen;
|
target_screen = screen;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let window_rect = {
|
let screen_frame = screen_frame.unwrap_or_else(|| {
|
||||||
let display_bounds = display.bounds();
|
let screen = NSScreen::mainScreen(nil);
|
||||||
if bounds.intersects(&display_bounds) {
|
target_screen = screen;
|
||||||
global_bounds_to_ns_rect(bounds)
|
NSScreen::visibleFrame(screen)
|
||||||
} else {
|
});
|
||||||
global_bounds_to_ns_rect(display_bounds)
|
|
||||||
}
|
let window_rect = NSRect::new(
|
||||||
};
|
NSPoint::new(
|
||||||
|
screen_frame.origin.x + bounds.origin.x.0 as f64,
|
||||||
|
screen_frame.origin.y
|
||||||
|
+ (display.bounds().size.height - bounds.origin.y).0 as f64,
|
||||||
|
),
|
||||||
|
NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
|
||||||
|
);
|
||||||
|
|
||||||
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
|
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
|
||||||
window_rect,
|
window_rect,
|
||||||
|
@ -572,7 +600,10 @@ impl MacWindow {
|
||||||
|
|
||||||
let window_size = {
|
let window_size = {
|
||||||
let scale = get_scale_factor(native_window);
|
let scale = get_scale_factor(native_window);
|
||||||
size(bounds.size.width.0 * scale, bounds.size.height.0 * scale)
|
size(
|
||||||
|
bounds.size.width.0 as f32 * scale,
|
||||||
|
bounds.size.height.0 as f32 * scale,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let window = Self(Arc::new(Mutex::new(MacWindowState {
|
let window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||||
|
@ -692,6 +723,11 @@ impl MacWindow {
|
||||||
native_window.orderFront_(nil);
|
native_window.orderFront_(nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the initial position of the window to the specified origin.
|
||||||
|
// Although we already specified the position using `initWithContentRect_styleMask_backing_defer_screen_`,
|
||||||
|
// the window position might be incorrect if the main screen (the screen that contains the window that has focus)
|
||||||
|
// is different from the primary screen.
|
||||||
|
NSWindow::setFrameTopLeftPoint_(native_window, window_rect.origin);
|
||||||
window.0.lock().move_traffic_light();
|
window.0.lock().move_traffic_light();
|
||||||
|
|
||||||
pool.drain();
|
pool.drain();
|
||||||
|
@ -737,7 +773,7 @@ impl Drop for MacWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformWindow for MacWindow {
|
impl PlatformWindow for MacWindow {
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.0.as_ref().lock().bounds()
|
self.0.as_ref().lock().bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use anyhow::{Ok, Result};
|
use anyhow::{Ok, Result};
|
||||||
|
|
||||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct TestDisplay {
|
pub(crate) struct TestDisplay {
|
||||||
id: DisplayId,
|
id: DisplayId,
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
bounds: Bounds<GlobalPixels>,
|
bounds: Bounds<DevicePixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestDisplay {
|
impl TestDisplay {
|
||||||
|
@ -16,7 +16,7 @@ impl TestDisplay {
|
||||||
uuid: uuid::Uuid::new_v4(),
|
uuid: uuid::Uuid::new_v4(),
|
||||||
bounds: Bounds::from_corners(
|
bounds: Bounds::from_corners(
|
||||||
Point::default(),
|
Point::default(),
|
||||||
Point::new(GlobalPixels(1920.), GlobalPixels(1080.)),
|
Point::new(DevicePixels(1920), DevicePixels(1080)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ impl PlatformDisplay for TestDisplay {
|
||||||
Ok(self.uuid)
|
Ok(self.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
|
fn bounds(&self) -> crate::Bounds<crate::DevicePixels> {
|
||||||
self.bounds
|
self.bounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult,
|
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels,
|
||||||
GlobalPixels, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
DispatchEventResult, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowParams,
|
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
|
||||||
|
WindowParams,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -12,7 +13,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct TestWindowState {
|
pub(crate) struct TestWindowState {
|
||||||
pub(crate) bounds: Bounds<GlobalPixels>,
|
pub(crate) bounds: Bounds<DevicePixels>,
|
||||||
pub(crate) handle: AnyWindowHandle,
|
pub(crate) handle: AnyWindowHandle,
|
||||||
display: Rc<dyn PlatformDisplay>,
|
display: Rc<dyn PlatformDisplay>,
|
||||||
pub(crate) title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
|
@ -78,7 +79,7 @@ impl TestWindow {
|
||||||
let Some(mut callback) = lock.resize_callback.take() else {
|
let Some(mut callback) = lock.resize_callback.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
lock.bounds.size = size.map(|pixels| f64::from(pixels).into());
|
lock.bounds.size = size.map(|pixels| (pixels.0 as i32).into());
|
||||||
drop(lock);
|
drop(lock);
|
||||||
callback(size, scale_factor);
|
callback(size, scale_factor);
|
||||||
self.0.lock().resize_callback = Some(callback);
|
self.0.lock().resize_callback = Some(callback);
|
||||||
|
@ -107,7 +108,7 @@ impl TestWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformWindow for TestWindow {
|
impl PlatformWindow for TestWindow {
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.0.lock().bounds
|
self.0.lock().bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,13 @@ use windows::{
|
||||||
Win32::{Foundation::*, Graphics::Gdi::*},
|
Win32::{Foundation::*, Graphics::Gdi::*},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
|
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point, Size};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct WindowsDisplay {
|
pub(crate) struct WindowsDisplay {
|
||||||
pub handle: HMONITOR,
|
pub handle: HMONITOR,
|
||||||
pub display_id: DisplayId,
|
pub display_id: DisplayId,
|
||||||
bounds: Bounds<GlobalPixels>,
|
bounds: Bounds<DevicePixels>,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +33,12 @@ impl WindowsDisplay {
|
||||||
display_id,
|
display_id,
|
||||||
bounds: Bounds {
|
bounds: Bounds {
|
||||||
origin: Point {
|
origin: Point {
|
||||||
x: GlobalPixels(size.left as f32),
|
x: DevicePixels(size.left as i32),
|
||||||
y: GlobalPixels(size.top as f32),
|
y: DevicePixels(size.top as i32),
|
||||||
},
|
},
|
||||||
size: Size {
|
size: Size {
|
||||||
width: GlobalPixels((size.right - size.left) as f32),
|
width: DevicePixels((size.right - size.left) as i32),
|
||||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
height: DevicePixels((size.bottom - size.top) as i32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -59,12 +59,12 @@ impl WindowsDisplay {
|
||||||
display_id: DisplayId(display_id as _),
|
display_id: DisplayId(display_id as _),
|
||||||
bounds: Bounds {
|
bounds: Bounds {
|
||||||
origin: Point {
|
origin: Point {
|
||||||
x: GlobalPixels(size.left as f32),
|
x: DevicePixels(size.left as i32),
|
||||||
y: GlobalPixels(size.top as f32),
|
y: DevicePixels(size.top as i32),
|
||||||
},
|
},
|
||||||
size: Size {
|
size: Size {
|
||||||
width: GlobalPixels((size.right - size.left) as f32),
|
width: DevicePixels((size.right - size.left) as i32),
|
||||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
height: DevicePixels((size.bottom - size.top) as i32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -81,12 +81,12 @@ impl WindowsDisplay {
|
||||||
display_id,
|
display_id,
|
||||||
bounds: Bounds {
|
bounds: Bounds {
|
||||||
origin: Point {
|
origin: Point {
|
||||||
x: GlobalPixels(size.left as f32),
|
x: DevicePixels(size.left as i32),
|
||||||
y: GlobalPixels(size.top as f32),
|
y: DevicePixels(size.top as i32),
|
||||||
},
|
},
|
||||||
size: Size {
|
size: Size {
|
||||||
width: GlobalPixels((size.right - size.left) as f32),
|
width: DevicePixels((size.right - size.left) as i32),
|
||||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
height: DevicePixels((size.bottom - size.top) as i32),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
uuid,
|
uuid,
|
||||||
|
@ -148,7 +148,7 @@ impl PlatformDisplay for WindowsDisplay {
|
||||||
Ok(self.uuid)
|
Ok(self.uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.bounds
|
self.bounds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,8 @@ use crate::*;
|
||||||
|
|
||||||
pub(crate) struct WindowsWindowInner {
|
pub(crate) struct WindowsWindowInner {
|
||||||
hwnd: HWND,
|
hwnd: HWND,
|
||||||
origin: Cell<Point<GlobalPixels>>,
|
origin: Cell<Point<DevicePixels>>,
|
||||||
physical_size: Cell<Size<GlobalPixels>>,
|
physical_size: Cell<Size<DevicePixels>>,
|
||||||
scale_factor: Cell<f32>,
|
scale_factor: Cell<f32>,
|
||||||
input_handler: Cell<Option<PlatformInputHandler>>,
|
input_handler: Cell<Option<PlatformInputHandler>>,
|
||||||
renderer: RefCell<BladeRenderer>,
|
renderer: RefCell<BladeRenderer>,
|
||||||
|
@ -68,12 +68,12 @@ impl WindowsWindowInner {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
|
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
|
||||||
let origin = Cell::new(Point {
|
let origin = Cell::new(Point {
|
||||||
x: GlobalPixels(cs.x as f32),
|
x: DevicePixels(cs.x as i32),
|
||||||
y: GlobalPixels(cs.y as f32),
|
y: DevicePixels(cs.y as i32),
|
||||||
});
|
});
|
||||||
let physical_size = Cell::new(Size {
|
let physical_size = Cell::new(Size {
|
||||||
width: GlobalPixels(cs.cx as f32),
|
width: DevicePixels(cs.cx as i32),
|
||||||
height: GlobalPixels(cs.cy as f32),
|
height: DevicePixels(cs.cy as i32),
|
||||||
});
|
});
|
||||||
let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
|
let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
|
||||||
let input_handler = Cell::new(None);
|
let input_handler = Cell::new(None);
|
||||||
|
@ -299,15 +299,15 @@ impl WindowsWindowInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
|
fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
|
||||||
let x = lparam.signed_loword() as f32;
|
let x = lparam.signed_loword() as i32;
|
||||||
let y = lparam.signed_hiword() as f32;
|
let y = lparam.signed_hiword() as i32;
|
||||||
self.origin.set(Point {
|
self.origin.set(Point {
|
||||||
x: GlobalPixels(x),
|
x: DevicePixels(x),
|
||||||
y: GlobalPixels(y),
|
y: DevicePixels(y),
|
||||||
});
|
});
|
||||||
let size = self.physical_size.get();
|
let size = self.physical_size.get();
|
||||||
let center_x = x + size.width.0 / 2.0;
|
let center_x = x + size.width.0 / 2;
|
||||||
let center_y = y + size.height.0 / 2.0;
|
let center_y = y + size.height.0 / 2;
|
||||||
let monitor_bounds = self.display.borrow().bounds();
|
let monitor_bounds = self.display.borrow().bounds();
|
||||||
if center_x < monitor_bounds.left().0
|
if center_x < monitor_bounds.left().0
|
||||||
|| center_x > monitor_bounds.right().0
|
|| center_x > monitor_bounds.right().0
|
||||||
|
@ -329,12 +329,12 @@ impl WindowsWindowInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
|
fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
|
||||||
let width = lparam.loword().max(1) as f32;
|
let width = lparam.loword().max(1) as i32;
|
||||||
let height = lparam.hiword().max(1) as f32;
|
let height = lparam.hiword().max(1) as i32;
|
||||||
let scale_factor = self.scale_factor.get();
|
let scale_factor = self.scale_factor.get();
|
||||||
let new_physical_size = Size {
|
let new_physical_size = Size {
|
||||||
width: GlobalPixels(width),
|
width: DevicePixels(width),
|
||||||
height: GlobalPixels(height),
|
height: DevicePixels(height),
|
||||||
};
|
};
|
||||||
self.physical_size.set(new_physical_size);
|
self.physical_size.set(new_physical_size);
|
||||||
self.renderer.borrow_mut().update_drawable_size(Size {
|
self.renderer.borrow_mut().update_drawable_size(Size {
|
||||||
|
@ -648,7 +648,7 @@ impl WindowsWindowInner {
|
||||||
if let Some(callback) = callbacks.input.as_mut() {
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
let x = lparam.signed_loword() as f32;
|
let x = lparam.signed_loword() as f32;
|
||||||
let y = lparam.signed_hiword() as f32;
|
let y = lparam.signed_hiword() as f32;
|
||||||
let physical_point = point(GlobalPixels(x), GlobalPixels(y));
|
let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
|
||||||
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||||
let scale_factor = self.scale_factor.get();
|
let scale_factor = self.scale_factor.get();
|
||||||
let event = MouseDownEvent {
|
let event = MouseDownEvent {
|
||||||
|
@ -924,8 +924,8 @@ impl WindowsWindowInner {
|
||||||
let height = size_rect.bottom - size_rect.top;
|
let height = size_rect.bottom - size_rect.top;
|
||||||
|
|
||||||
self.physical_size.set(Size {
|
self.physical_size.set(Size {
|
||||||
width: GlobalPixels(width as f32),
|
width: DevicePixels(width as i32),
|
||||||
height: GlobalPixels(height as f32),
|
height: DevicePixels(height as i32),
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.hide_title_bar {
|
if self.hide_title_bar {
|
||||||
|
@ -1077,8 +1077,8 @@ impl WindowsWindowInner {
|
||||||
};
|
};
|
||||||
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
let physical_point = point(
|
let physical_point = point(
|
||||||
GlobalPixels(cursor_point.x as f32),
|
DevicePixels(cursor_point.x as i32),
|
||||||
GlobalPixels(cursor_point.y as f32),
|
DevicePixels(cursor_point.y as i32),
|
||||||
);
|
);
|
||||||
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||||
let scale_factor = self.scale_factor.get();
|
let scale_factor = self.scale_factor.get();
|
||||||
|
@ -1305,7 +1305,7 @@ impl Drop for WindowsWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlatformWindow for WindowsWindow {
|
impl PlatformWindow for WindowsWindow {
|
||||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||||
Bounds {
|
Bounds {
|
||||||
origin: self.inner.origin.get(),
|
origin: self.inner.origin.get(),
|
||||||
size: self.inner.physical_size.get(),
|
size: self.inner.physical_size.get(),
|
||||||
|
@ -1674,7 +1674,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler {
|
||||||
struct ClickState {
|
struct ClickState {
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
last_click: Instant,
|
last_click: Instant,
|
||||||
last_position: Point<GlobalPixels>,
|
last_position: Point<DevicePixels>,
|
||||||
current_count: usize,
|
current_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1689,7 +1689,7 @@ impl ClickState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update self and return the needed click count
|
/// update self and return the needed click count
|
||||||
pub fn update(&mut self, button: MouseButton, new_position: Point<GlobalPixels>) -> usize {
|
pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
|
||||||
if self.button == button && self.is_double_click(new_position) {
|
if self.button == button && self.is_double_click(new_position) {
|
||||||
self.current_count += 1;
|
self.current_count += 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1703,7 +1703,7 @@ impl ClickState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_double_click(&self, new_position: Point<GlobalPixels>) -> bool {
|
fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
|
||||||
let diff = self.last_position - new_position;
|
let diff = self.last_position - new_position;
|
||||||
|
|
||||||
self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
|
self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
|
||||||
|
@ -1839,10 +1839,10 @@ fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn logical_size(physical_size: Size<GlobalPixels>, scale_factor: f32) -> Size<Pixels> {
|
fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
|
||||||
Size {
|
Size {
|
||||||
width: px(physical_size.width.0 / scale_factor),
|
width: px(physical_size.width.0 as f32 / scale_factor),
|
||||||
height: px(physical_size.height.0 / scale_factor),
|
height: px(physical_size.height.0 as f32 / scale_factor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1867,51 +1867,36 @@ const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
|
// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
|
||||||
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
|
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
||||||
const DOUBLE_CLICK_SPATIAL_TOLERANCE: f32 = 4.0;
|
const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::ClickState;
|
use super::ClickState;
|
||||||
use crate::{point, GlobalPixels, MouseButton};
|
use crate::{point, DevicePixels, MouseButton};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_double_click_interval() {
|
fn test_double_click_interval() {
|
||||||
let mut state = ClickState::new();
|
let mut state = ClickState::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
|
||||||
MouseButton::Right,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
state.last_click -= Duration::from_millis(700);
|
state.last_click -= Duration::from_millis(700);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1920,31 +1905,19 @@ mod tests {
|
||||||
fn test_double_click_spatial_tolerance() {
|
fn test_double_click_spatial_tolerance() {
|
||||||
let mut state = ClickState::new();
|
let mut state = ClickState::new();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(-3.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
|
||||||
MouseButton::Left,
|
|
||||||
point(GlobalPixels(0.0), GlobalPixels(3.0))
|
|
||||||
),
|
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
|
||||||
MouseButton::Right,
|
|
||||||
point(GlobalPixels(3.0), GlobalPixels(2.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.update(
|
state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
|
||||||
MouseButton::Right,
|
|
||||||
point(GlobalPixels(10.0), GlobalPixels(0.0))
|
|
||||||
),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena,
|
point, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena,
|
||||||
AsyncWindowContext, Bounds, Context, Corners, CursorStyle, DispatchActionListener,
|
AsyncWindowContext, Bounds, Context, Corners, CursorStyle, DevicePixels,
|
||||||
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
|
DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
|
||||||
FileDropEvent, Flatten, Global, GlobalElementId, GlobalPixels, Hsla, KeyBinding, KeyDownEvent,
|
EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId, Hsla, KeyBinding,
|
||||||
KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers,
|
KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext,
|
||||||
ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
|
Modifiers, ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||||
PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
|
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render,
|
||||||
SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
|
ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task,
|
||||||
TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowOptions,
|
TextStyle, TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowOptions,
|
||||||
WindowParams, WindowTextSystem,
|
WindowParams, WindowTextSystem,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
|
@ -358,26 +358,27 @@ pub(crate) struct ElementStateBox {
|
||||||
pub(crate) type_name: &'static str,
|
pub(crate) type_name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_bounds(cx: &mut AppContext) -> Bounds<GlobalPixels> {
|
fn default_bounds(display_id: Option<DisplayId>, cx: &mut AppContext) -> Bounds<DevicePixels> {
|
||||||
const DEFAULT_WINDOW_SIZE: Size<GlobalPixels> = size(GlobalPixels(1024.0), GlobalPixels(700.0));
|
const DEFAULT_WINDOW_SIZE: Size<DevicePixels> = size(DevicePixels(1024), DevicePixels(700));
|
||||||
const DEFAULT_WINDOW_OFFSET: Point<GlobalPixels> = point(GlobalPixels(0.0), GlobalPixels(35.0));
|
const DEFAULT_WINDOW_OFFSET: Point<DevicePixels> = point(DevicePixels(0), DevicePixels(35));
|
||||||
|
|
||||||
cx.active_window()
|
cx.active_window()
|
||||||
.and_then(|w| w.update(cx, |_, cx| cx.window_bounds()).ok())
|
.and_then(|w| w.update(cx, |_, cx| cx.window_bounds()).ok())
|
||||||
.map(|bounds| bounds.map_origin(|origin| origin + DEFAULT_WINDOW_OFFSET))
|
.map(|bounds| bounds.map_origin(|origin| origin + DEFAULT_WINDOW_OFFSET))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
cx.primary_display()
|
let display = display_id
|
||||||
|
.map(|id| cx.find_display(id))
|
||||||
|
.unwrap_or_else(|| cx.primary_display());
|
||||||
|
|
||||||
|
display
|
||||||
.map(|display| {
|
.map(|display| {
|
||||||
let center = display.bounds().center();
|
let center = display.bounds().center();
|
||||||
let offset = DEFAULT_WINDOW_SIZE / 2.0;
|
let offset = DEFAULT_WINDOW_SIZE / 2;
|
||||||
let origin = point(center.x - offset.width, center.y - offset.height);
|
let origin = point(center.x - offset.width, center.y - offset.height);
|
||||||
Bounds::new(origin, DEFAULT_WINDOW_SIZE)
|
Bounds::new(origin, DEFAULT_WINDOW_SIZE)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
Bounds::new(
|
Bounds::new(point(DevicePixels(0), DevicePixels(0)), DEFAULT_WINDOW_SIZE)
|
||||||
point(GlobalPixels(0.0), GlobalPixels(0.0)),
|
|
||||||
DEFAULT_WINDOW_SIZE,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -399,7 +400,7 @@ impl Window {
|
||||||
fullscreen,
|
fullscreen,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let bounds = bounds.unwrap_or_else(|| default_bounds(cx));
|
let bounds = bounds.unwrap_or_else(|| default_bounds(display_id, cx));
|
||||||
let platform_window = cx.platform.open_window(
|
let platform_window = cx.platform.open_window(
|
||||||
handle,
|
handle,
|
||||||
WindowParams {
|
WindowParams {
|
||||||
|
@ -867,7 +868,7 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
|
/// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
|
||||||
pub fn window_bounds(&self) -> Bounds<GlobalPixels> {
|
pub fn window_bounds(&self) -> Bounds<DevicePixels> {
|
||||||
self.window.platform_window.bounds()
|
self.window.platform_window.bounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ fn main() {
|
||||||
cx.set_menus(app_menus());
|
cx.set_menus(app_menus());
|
||||||
|
|
||||||
let size = size(px(1500.), px(780.));
|
let size = size(px(1500.), px(780.));
|
||||||
let bounds = Bounds::centered(size, cx);
|
let bounds = Bounds::centered(None, size, cx);
|
||||||
let _window = cx.open_window(
|
let _window = cx.open_window(
|
||||||
WindowOptions {
|
WindowOptions {
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl sqlez::bindable::Column for SerializedAxis {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds<gpui::GlobalPixels>);
|
pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds<gpui::DevicePixels>);
|
||||||
|
|
||||||
impl StaticColumnCount for SerializedWindowsBounds {
|
impl StaticColumnCount for SerializedWindowsBounds {
|
||||||
fn column_count() -> usize {
|
fn column_count() -> usize {
|
||||||
|
@ -73,10 +73,10 @@ impl Bind for SerializedWindowsBounds {
|
||||||
|
|
||||||
statement.bind(
|
statement.bind(
|
||||||
&(
|
&(
|
||||||
SerializedGlobalPixels(self.0.origin.x),
|
SerializedDevicePixels(self.0.origin.x),
|
||||||
SerializedGlobalPixels(self.0.origin.y),
|
SerializedDevicePixels(self.0.origin.y),
|
||||||
SerializedGlobalPixels(self.0.size.width),
|
SerializedDevicePixels(self.0.size.width),
|
||||||
SerializedGlobalPixels(self.0.size.height),
|
SerializedDevicePixels(self.0.size.height),
|
||||||
),
|
),
|
||||||
next_index,
|
next_index,
|
||||||
)
|
)
|
||||||
|
@ -89,10 +89,10 @@ impl Column for SerializedWindowsBounds {
|
||||||
let bounds = match window_state.as_str() {
|
let bounds = match window_state.as_str() {
|
||||||
"Fixed" => {
|
"Fixed" => {
|
||||||
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
|
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
|
||||||
let x: f64 = x;
|
let x: i32 = x;
|
||||||
let y: f64 = y;
|
let y: i32 = y;
|
||||||
let width: f64 = width;
|
let width: i32 = width;
|
||||||
let height: f64 = height;
|
let height: i32 = height;
|
||||||
SerializedWindowsBounds(Bounds {
|
SerializedWindowsBounds(Bounds {
|
||||||
origin: point(x.into(), y.into()),
|
origin: point(x.into(), y.into()),
|
||||||
size: size(width.into(), height.into()),
|
size: size(width.into(), height.into()),
|
||||||
|
@ -106,17 +106,16 @@ impl Column for SerializedWindowsBounds {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
struct SerializedGlobalPixels(gpui::GlobalPixels);
|
struct SerializedDevicePixels(gpui::DevicePixels);
|
||||||
impl sqlez::bindable::StaticColumnCount for SerializedGlobalPixels {}
|
impl sqlez::bindable::StaticColumnCount for SerializedDevicePixels {}
|
||||||
|
|
||||||
impl sqlez::bindable::Bind for SerializedGlobalPixels {
|
impl sqlez::bindable::Bind for SerializedDevicePixels {
|
||||||
fn bind(
|
fn bind(
|
||||||
&self,
|
&self,
|
||||||
statement: &sqlez::statement::Statement,
|
statement: &sqlez::statement::Statement,
|
||||||
start_index: i32,
|
start_index: i32,
|
||||||
) -> anyhow::Result<i32> {
|
) -> anyhow::Result<i32> {
|
||||||
let this: f64 = self.0.into();
|
let this: i32 = self.0.into();
|
||||||
let this: f32 = this as _;
|
|
||||||
this.bind(statement, start_index)
|
this.bind(statement, start_index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use db::sqlez::{
|
||||||
bindable::{Bind, Column, StaticColumnCount},
|
bindable::{Bind, Column, StaticColumnCount},
|
||||||
statement::Statement,
|
statement::Statement,
|
||||||
};
|
};
|
||||||
use gpui::{AsyncWindowContext, Bounds, GlobalPixels, Model, Task, View, WeakView};
|
use gpui::{AsyncWindowContext, Bounds, DevicePixels, Model, Task, View, WeakView};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -69,7 +69,7 @@ pub(crate) struct SerializedWorkspace {
|
||||||
pub(crate) id: WorkspaceId,
|
pub(crate) id: WorkspaceId,
|
||||||
pub(crate) location: WorkspaceLocation,
|
pub(crate) location: WorkspaceLocation,
|
||||||
pub(crate) center_group: SerializedPaneGroup,
|
pub(crate) center_group: SerializedPaneGroup,
|
||||||
pub(crate) bounds: Option<Bounds<GlobalPixels>>,
|
pub(crate) bounds: Option<Bounds<DevicePixels>>,
|
||||||
pub(crate) fullscreen: bool,
|
pub(crate) fullscreen: bool,
|
||||||
pub(crate) display: Option<Uuid>,
|
pub(crate) display: Option<Uuid>,
|
||||||
pub(crate) docks: DockStructure,
|
pub(crate) docks: DockStructure,
|
||||||
|
|
|
@ -27,8 +27,8 @@ use futures::{
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
|
actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
|
||||||
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId,
|
AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
|
||||||
EventEmitter, FocusHandle, FocusableView, Global, GlobalPixels, KeyContext, Keystroke,
|
Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke,
|
||||||
LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
|
LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
|
||||||
Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
|
Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
|
@ -89,11 +89,11 @@ use crate::persistence::{
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
|
static ref ZED_WINDOW_SIZE: Option<Size<DevicePixels>> = env::var("ZED_WINDOW_SIZE")
|
||||||
.ok()
|
.ok()
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(parse_pixel_size_env_var);
|
.and_then(parse_pixel_size_env_var);
|
||||||
static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
|
static ref ZED_WINDOW_POSITION: Option<Point<DevicePixels>> = env::var("ZED_WINDOW_POSITION")
|
||||||
.ok()
|
.ok()
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.and_then(parse_pixel_position_env_var);
|
.and_then(parse_pixel_position_env_var);
|
||||||
|
@ -745,11 +745,7 @@ impl Workspace {
|
||||||
cx.observe_window_activation(Self::on_window_activation_changed),
|
cx.observe_window_activation(Self::on_window_activation_changed),
|
||||||
cx.observe_window_bounds(move |_, cx| {
|
cx.observe_window_bounds(move |_, cx| {
|
||||||
if let Some(display) = cx.display() {
|
if let Some(display) = cx.display() {
|
||||||
// Transform fixed bounds to be stored in terms of the containing display
|
let window_bounds = cx.window_bounds();
|
||||||
let mut window_bounds = cx.window_bounds();
|
|
||||||
let display_bounds = display.bounds();
|
|
||||||
window_bounds.origin.x -= display_bounds.origin.x;
|
|
||||||
window_bounds.origin.y -= display_bounds.origin.y;
|
|
||||||
let fullscreen = cx.is_fullscreen();
|
let fullscreen = cx.is_fullscreen();
|
||||||
|
|
||||||
if let Some(display_uuid) = display.uuid().log_err() {
|
if let Some(display_uuid) = display.uuid().log_err() {
|
||||||
|
@ -902,7 +898,7 @@ impl Workspace {
|
||||||
})?;
|
})?;
|
||||||
window
|
window
|
||||||
} else {
|
} else {
|
||||||
let window_bounds_override = window_bounds_env_override(&cx);
|
let window_bounds_override = window_bounds_env_override();
|
||||||
|
|
||||||
let (bounds, display, fullscreen) = if let Some(bounds) = window_bounds_override {
|
let (bounds, display, fullscreen) = if let Some(bounds) = window_bounds_override {
|
||||||
(Some(bounds), None, false)
|
(Some(bounds), None, false)
|
||||||
|
@ -917,24 +913,7 @@ impl Workspace {
|
||||||
Some((display?, bounds?.0, fullscreen.unwrap_or(false)))
|
Some((display?, bounds?.0, fullscreen.unwrap_or(false)))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((serialized_display, mut bounds, fullscreen)) = restorable_bounds {
|
if let Some((serialized_display, bounds, fullscreen)) = restorable_bounds {
|
||||||
// Stored bounds are relative to the containing display.
|
|
||||||
// So convert back to global coordinates if that screen still exists
|
|
||||||
let screen_bounds = cx
|
|
||||||
.update(|cx| {
|
|
||||||
cx.displays()
|
|
||||||
.into_iter()
|
|
||||||
.find(|display| display.uuid().ok() == Some(serialized_display))
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|screen| screen.bounds());
|
|
||||||
|
|
||||||
if let Some(screen_bounds) = screen_bounds {
|
|
||||||
bounds.origin.x += screen_bounds.origin.x;
|
|
||||||
bounds.origin.y += screen_bounds.origin.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
(Some(bounds), Some(serialized_display), fullscreen)
|
(Some(bounds), Some(serialized_display), fullscreen)
|
||||||
} else {
|
} else {
|
||||||
(None, None, false)
|
(None, None, false)
|
||||||
|
@ -3756,14 +3735,11 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<Bounds<GlobalPixels>> {
|
fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
|
||||||
let display_origin = cx
|
|
||||||
.update(|cx| Some(cx.displays().first()?.bounds().origin))
|
|
||||||
.ok()??;
|
|
||||||
ZED_WINDOW_POSITION
|
ZED_WINDOW_POSITION
|
||||||
.zip(*ZED_WINDOW_SIZE)
|
.zip(*ZED_WINDOW_SIZE)
|
||||||
.map(|(position, size)| Bounds {
|
.map(|(position, size)| Bounds {
|
||||||
origin: display_origin + position,
|
origin: position,
|
||||||
size,
|
size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4662,7 +4638,7 @@ pub fn join_hosted_project(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let window_bounds_override = window_bounds_env_override(&cx);
|
let window_bounds_override = window_bounds_env_override();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let mut options = (app_state.build_window_options)(None, cx);
|
let mut options = (app_state.build_window_options)(None, cx);
|
||||||
options.bounds = window_bounds_override;
|
options.bounds = window_bounds_override;
|
||||||
|
@ -4723,7 +4699,7 @@ pub fn join_in_room_project(
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let window_bounds_override = window_bounds_env_override(&cx);
|
let window_bounds_override = window_bounds_env_override();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let mut options = (app_state.build_window_options)(None, cx);
|
let mut options = (app_state.build_window_options)(None, cx);
|
||||||
options.bounds = window_bounds_override;
|
options.bounds = window_bounds_override;
|
||||||
|
@ -4817,18 +4793,18 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
|
fn parse_pixel_position_env_var(value: &str) -> Option<Point<DevicePixels>> {
|
||||||
let mut parts = value.split(',');
|
let mut parts = value.split(',');
|
||||||
let x: usize = parts.next()?.parse().ok()?;
|
let x: usize = parts.next()?.parse().ok()?;
|
||||||
let y: usize = parts.next()?.parse().ok()?;
|
let y: usize = parts.next()?.parse().ok()?;
|
||||||
Some(point((x as f64).into(), (y as f64).into()))
|
Some(point((x as i32).into(), (y as i32).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
|
fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
|
||||||
let mut parts = value.split(',');
|
let mut parts = value.split(',');
|
||||||
let width: usize = parts.next()?.parse().ok()?;
|
let width: usize = parts.next()?.parse().ok()?;
|
||||||
let height: usize = parts.next()?.parse().ok()?;
|
let height: usize = parts.next()?.parse().ok()?;
|
||||||
Some(size((width as f64).into(), (height as f64).into()))
|
Some(size((width as i32).into(), (height as i32).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DisconnectedOverlay;
|
struct DisconnectedOverlay;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue