Compare commits
8 commits
4a166606c0
...
be57d63b48
| Author | SHA1 | Date | |
|---|---|---|---|
| be57d63b48 | |||
| ea57a8277a | |||
| 84ef88c1f1 | |||
| 3a50f77beb | |||
| 54480252a0 | |||
| b2702afcb4 | |||
| 3894f30797 | |||
| 3c91feeb58 |
19 changed files with 89 additions and 1274 deletions
|
|
@ -52,7 +52,6 @@ mod performance_stats;
|
||||||
pub mod memory_watchdog;
|
pub mod memory_watchdog;
|
||||||
pub mod perf_monitor;
|
pub mod perf_monitor;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
mod screen;
|
|
||||||
mod texture;
|
mod texture;
|
||||||
mod uniform_buffer;
|
mod uniform_buffer;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
@ -216,7 +215,6 @@ pub use {
|
||||||
unregister_media_playback_session, MediaPlaybackSessionId,
|
unregister_media_playback_session, MediaPlaybackSessionId,
|
||||||
},
|
},
|
||||||
script::vm::*,
|
script::vm::*,
|
||||||
screen::{fit_window_rect_to_screens, ScreenGeom, MIN_WINDOW_SIZE},
|
|
||||||
shared_bytes::{MappedBytes, SharedBytes, SharedBytesStats},
|
shared_bytes::{MappedBytes, SharedBytes, SharedBytesStats},
|
||||||
texture::{
|
texture::{
|
||||||
image_cache_use_mipmaps, Texture, TextureAnimation, TextureFormat, TextureId,
|
image_cache_use_mipmaps, Texture, TextureAnimation, TextureFormat, TextureId,
|
||||||
|
|
|
||||||
|
|
@ -1539,12 +1539,11 @@ impl Cx {
|
||||||
match op {
|
match op {
|
||||||
CxOsOp::CreateWindow(window_id) => {
|
CxOsOp::CreateWindow(window_id) => {
|
||||||
let window = &mut self.windows[window_id];
|
let window = &mut self.windows[window_id];
|
||||||
let (create_position, create_inner_size) = window.create_geom();
|
|
||||||
let mut metal_window = MetalWindow::new(
|
let mut metal_window = MetalWindow::new(
|
||||||
window_id,
|
window_id,
|
||||||
&metal_cx,
|
&metal_cx,
|
||||||
create_inner_size,
|
window.create_inner_size.unwrap_or(dvec2(800., 600.)),
|
||||||
create_position,
|
window.create_position,
|
||||||
&window.create_title,
|
&window.create_title,
|
||||||
window.is_fullscreen,
|
window.is_fullscreen,
|
||||||
window.macos,
|
window.macos,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use {
|
||||||
MouseUpEvent, ScrollEvent, ScrollPhase, TextInputEvent, WindowCloseRequestedEvent,
|
MouseUpEvent, ScrollEvent, ScrollPhase, TextInputEvent, WindowCloseRequestedEvent,
|
||||||
WindowDragQueryEvent, WindowDragQueryResponse, WindowGeom, WindowGeomChangeEvent,
|
WindowDragQueryEvent, WindowDragQueryResponse, WindowGeom, WindowGeomChangeEvent,
|
||||||
},
|
},
|
||||||
makepad_math::{dvec2, Rect, Vec2d},
|
makepad_math::{Rect, Vec2d},
|
||||||
os::{
|
os::{
|
||||||
apple::apple_sys::*,
|
apple::apple_sys::*,
|
||||||
apple::apple_util::str_to_nsstring,
|
apple::apple_util::str_to_nsstring,
|
||||||
|
|
@ -18,7 +18,6 @@ use {
|
||||||
macos_event::MacosEvent,
|
macos_event::MacosEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screen::{clamp_point_to_screens, fit_window_rect_to_screens, ScreenGeom},
|
|
||||||
window::{
|
window::{
|
||||||
MacosWindowChrome, MacosWindowConfig, MacosWindowKind, MacosWindowLevel,
|
MacosWindowChrome, MacosWindowConfig, MacosWindowKind, MacosWindowLevel,
|
||||||
WindowBackdrop, WindowId, WindowVisuals,
|
WindowBackdrop, WindowId, WindowVisuals,
|
||||||
|
|
@ -256,13 +255,9 @@ impl MacosWindow {
|
||||||
let () = msg_send![self.view, setAllowedTouchTypes: 2u64];
|
let () = msg_send![self.view, setAllowedTouchTypes: 2u64];
|
||||||
|
|
||||||
let left_top = if let Some(position) = position {
|
let left_top = if let Some(position) = position {
|
||||||
// A restored position can name a display that is gone. Pinning it before the
|
|
||||||
// window is built keeps it from being ordered on screen somewhere unreachable;
|
|
||||||
// `fit_to_screens` below corrects the finished frame.
|
|
||||||
let pinned = clamp_point_to_screens(&macos_screens(), position);
|
|
||||||
NSPoint {
|
NSPoint {
|
||||||
x: pinned.x,
|
x: position.x as f64,
|
||||||
y: pinned.y,
|
y: position.y as f64,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NSPoint { x: 0., y: 0. }
|
NSPoint { x: 0., y: 0. }
|
||||||
|
|
@ -355,11 +350,6 @@ impl MacosWindow {
|
||||||
if position.is_none() {
|
if position.is_none() {
|
||||||
let () = msg_send![self.window, center];
|
let () = msg_send![self.window, center];
|
||||||
}
|
}
|
||||||
if !is_fullscreen {
|
|
||||||
// A restored size and position are only as good as the display arrangement
|
|
||||||
// they were saved on; a fullscreen window is AppKit's to place.
|
|
||||||
self.fit_to_screens();
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_context: ObjcId = msg_send![self.view, inputContext];
|
let input_context: ObjcId = msg_send![self.view, inputContext];
|
||||||
let () = msg_send![input_context, invalidateCharacterCoordinates];
|
let () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||||
|
|
@ -773,34 +763,12 @@ impl MacosWindow {
|
||||||
let mut window_frame: NSRect = unsafe { msg_send![self.window, frame] };
|
let mut window_frame: NSRect = unsafe { msg_send![self.window, frame] };
|
||||||
window_frame.origin.x = pos.x as f64;
|
window_frame.origin.x = pos.x as f64;
|
||||||
window_frame.origin.y = pos.y as f64;
|
window_frame.origin.y = pos.y as f64;
|
||||||
// A caller placing the window cannot know the display arrangement it is placing
|
//not very nice: CGDisplay::main().pixels_high() as f64
|
||||||
// into, so the request is fitted to the displays that are actually attached.
|
|
||||||
let fitted = fit_window_rect_to_screens(&macos_screens(), rect_of(window_frame));
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let () = msg_send![self.window, setFrame: ns_rect_of(fitted) display: YES];
|
let () = msg_send![self.window, setFrame: window_frame display: YES];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves and resizes the window so it sits entirely within one display's visible frame.
|
|
||||||
///
|
|
||||||
/// See `crate::screen::fit_window_rect_to_screens` for what counts as a fit and why it
|
|
||||||
/// is unconditional. A window that already fits is left untouched.
|
|
||||||
pub fn fit_to_screens(&mut self) {
|
|
||||||
let screens = macos_screens();
|
|
||||||
if screens.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let frame: NSRect = unsafe { msg_send![self.window, frame] };
|
|
||||||
let current = rect_of(frame);
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, current);
|
|
||||||
if fitted == current {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let () = msg_send![self.window, setFrame: ns_rect_of(fitted) display: YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position(&self) -> Vec2d {
|
pub fn get_position(&self) -> Vec2d {
|
||||||
let window_frame: NSRect = unsafe { msg_send![self.window, frame] };
|
let window_frame: NSRect = unsafe { msg_send![self.window, frame] };
|
||||||
Vec2d {
|
Vec2d {
|
||||||
|
|
@ -1215,52 +1183,3 @@ pub fn get_cocoa_window(this: &Object) -> &mut MacosWindow {
|
||||||
&mut *(ptr as *mut MacosWindow)
|
&mut *(ptr as *mut MacosWindow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts an `NSRect` to makepad's rectangle, leaving Cocoa's bottom-left origin as it is.
|
|
||||||
fn rect_of(r: NSRect) -> Rect {
|
|
||||||
Rect {
|
|
||||||
pos: dvec2(r.origin.x, r.origin.y),
|
|
||||||
size: dvec2(r.size.width, r.size.height),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts makepad's rectangle back to an `NSRect`.
|
|
||||||
fn ns_rect_of(r: Rect) -> NSRect {
|
|
||||||
NSRect {
|
|
||||||
origin: NSPoint {
|
|
||||||
x: r.pos.x,
|
|
||||||
y: r.pos.y,
|
|
||||||
},
|
|
||||||
size: NSSize {
|
|
||||||
width: r.size.x,
|
|
||||||
height: r.size.y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The displays currently attached, in Cocoa's global point space (bottom-left origin) —
|
|
||||||
/// the space an `NSWindow` frame is expressed in.
|
|
||||||
pub fn macos_screens() -> Vec<ScreenGeom> {
|
|
||||||
unsafe {
|
|
||||||
let screens: ObjcId = msg_send![class!(NSScreen), screens];
|
|
||||||
let count: usize = msg_send![screens, count];
|
|
||||||
let mut out = Vec::with_capacity(count);
|
|
||||||
for index in 0..count {
|
|
||||||
let screen: ObjcId = msg_send![screens, objectAtIndex: index];
|
|
||||||
if screen == nil {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let frame: NSRect = msg_send![screen, frame];
|
|
||||||
let visible: NSRect = msg_send![screen, visibleFrame];
|
|
||||||
out.push(ScreenGeom {
|
|
||||||
bounds: rect_of(frame),
|
|
||||||
work_area: rect_of(visible),
|
|
||||||
// Element zero of `NSScreen.screens` is the display holding the menu bar,
|
|
||||||
// which is the one Cocoa places windows against; `mainScreen` follows the
|
|
||||||
// key window instead and would move under the app.
|
|
||||||
is_primary: index == 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
use {
|
use {
|
||||||
crate::cx::Cx,
|
crate::cx::Cx,
|
||||||
std::{
|
std::{fs::File, io::prelude::*, rc::Rc, time::SystemTime},
|
||||||
fs::File,
|
|
||||||
io::prelude::*,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
rc::Rc,
|
|
||||||
sync::OnceLock,
|
|
||||||
time::SystemTime,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
|
@ -17,58 +10,21 @@ pub enum EventFlow {
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The directory holding the running executable, queried once.
|
|
||||||
fn exe_dir() -> Option<&'static Path> {
|
|
||||||
static EXE_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
|
|
||||||
EXE_DIR
|
|
||||||
.get_or_init(|| {
|
|
||||||
std::env::current_exe()
|
|
||||||
.ok()
|
|
||||||
.and_then(|exe| exe.parent().map(Path::to_path_buf))
|
|
||||||
})
|
|
||||||
.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolves a relative resource path against the directory holding the executable.
|
|
||||||
///
|
|
||||||
/// Packaged desktop layouts ship resources beside the executable and address them through a
|
|
||||||
/// relative package root, which a plain relative open resolves against the process working
|
|
||||||
/// directory instead. Any launcher that does not set a working directory — a URL-protocol
|
|
||||||
/// handler, a file association, a service, a shortcut without one — then starts the app in an
|
|
||||||
/// unrelated directory and every resource open fails, leaving a window that draws its shapes
|
|
||||||
/// but has no fonts, icons or images. Callers retry through here so the executable's own
|
|
||||||
/// directory is searched as well. Returns `None` for an absolute path (already anchored) and
|
|
||||||
/// when the executable path is unavailable.
|
|
||||||
pub fn exe_relative_path(rel: impl AsRef<Path>) -> Option<PathBuf> {
|
|
||||||
let rel = rel.as_ref();
|
|
||||||
if rel.is_absolute() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(exe_dir()?.join(rel))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a file at `path`, falling back to the same path resolved against the executable's
|
|
||||||
/// directory. Returns `None` when neither location holds a readable file.
|
|
||||||
pub fn read_file_cwd_or_exe_relative(path: impl AsRef<Path>) -> Option<Vec<u8>> {
|
|
||||||
fn read(path: &Path) -> Option<Vec<u8>> {
|
|
||||||
let mut buffer = Vec::<u8>::new();
|
|
||||||
File::open(path).ok()?.read_to_end(&mut buffer).ok()?;
|
|
||||||
Some(buffer)
|
|
||||||
}
|
|
||||||
let path = path.as_ref();
|
|
||||||
read(path).or_else(|| read(&exe_relative_path(path)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
// lets start a websocket thread
|
// lets start a websocket thread
|
||||||
|
|
||||||
impl Cx {
|
impl Cx {
|
||||||
pub fn native_load_dependencies(&mut self) {
|
pub fn native_load_dependencies(&mut self) {
|
||||||
for (path, dep) in &mut self.dependencies {
|
for (path, dep) in &mut self.dependencies {
|
||||||
if let Some(buffer) = read_file_cwd_or_exe_relative(path) {
|
if let Ok(mut file_handle) = File::open(path) {
|
||||||
|
let mut buffer = Vec::<u8>::new();
|
||||||
|
if file_handle.read_to_end(&mut buffer).is_ok() {
|
||||||
dep.data = Some(Ok(Rc::new(buffer)));
|
dep.data = Some(Ok(Rc::new(buffer)));
|
||||||
|
} else {
|
||||||
|
dep.data = Some(Err("read_to_end failed".to_string()));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Could not load resource {}", path);
|
println!("Could not load resource {}", path);
|
||||||
dep.data = Some(Err(format!("Could not read resource {}", path)));
|
dep.data = Some(Err("File! open failed".to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -633,13 +633,10 @@ impl Cx {
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = &mut self.windows[window_id];
|
let window = &mut self.windows[window_id];
|
||||||
let (position, inner_size) = window.create_geom();
|
let inner_size = window
|
||||||
let inner_size = if window.create_inner_size.is_some() {
|
.create_inner_size
|
||||||
inner_size
|
.unwrap_or_else(|| dvec2(1920.0, 1080.0));
|
||||||
} else {
|
let position = window.create_position.unwrap_or_else(|| dvec2(0.0, 0.0));
|
||||||
dvec2(1920.0, 1080.0)
|
|
||||||
};
|
|
||||||
let position = position.unwrap_or_else(|| dvec2(0.0, 0.0));
|
|
||||||
let dpi_factor = configured_headless_dpi();
|
let dpi_factor = configured_headless_dpi();
|
||||||
|
|
||||||
let state = &mut windows[window_id.id()];
|
let state = &mut windows[window_id.id()];
|
||||||
|
|
|
||||||
|
|
@ -634,7 +634,6 @@ impl WaylandCx {
|
||||||
let compositor = state.compositor.as_ref().unwrap();
|
let compositor = state.compositor.as_ref().unwrap();
|
||||||
let wm_base = state.wm_base.as_ref().unwrap();
|
let wm_base = state.wm_base.as_ref().unwrap();
|
||||||
let window = &cx.windows[window_id];
|
let window = &cx.windows[window_id];
|
||||||
let (create_position, create_inner_size) = window.create_geom();
|
|
||||||
let app_id = if window.create_app_id.is_empty() {
|
let app_id = if window.create_app_id.is_empty() {
|
||||||
"Makepad"
|
"Makepad"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -651,8 +650,8 @@ impl WaylandCx {
|
||||||
state.shm.as_ref(),
|
state.shm.as_ref(),
|
||||||
self.qhandle.as_ref().unwrap(),
|
self.qhandle.as_ref().unwrap(),
|
||||||
gl_cx,
|
gl_cx,
|
||||||
create_inner_size,
|
window.create_inner_size.unwrap_or(dvec2(800., 600.)),
|
||||||
create_position,
|
window.create_position,
|
||||||
&window.create_title,
|
&window.create_title,
|
||||||
app_id,
|
app_id,
|
||||||
window.is_fullscreen,
|
window.is_fullscreen,
|
||||||
|
|
@ -766,9 +765,6 @@ impl WaylandCx {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CxOsOp::ResizeWindow(window_id, size) => {}
|
CxOsOp::ResizeWindow(window_id, size) => {}
|
||||||
// A Wayland client is not told where its windows are and cannot move them;
|
|
||||||
// the compositor owns placement, so a window here is never left off-screen
|
|
||||||
// by a restored position the way it can be on Windows, macOS and X11.
|
|
||||||
CxOsOp::RepositionWindow(window_id, size) => {}
|
CxOsOp::RepositionWindow(window_id, size) => {}
|
||||||
CxOsOp::SetWindowVisuals(_window_id, visuals) => {
|
CxOsOp::SetWindowVisuals(_window_id, visuals) => {
|
||||||
if visuals.backdrop != crate::window::WindowBackdrop::None {
|
if visuals.backdrop != crate::window::WindowBackdrop::None {
|
||||||
|
|
|
||||||
|
|
@ -87,19 +87,8 @@ impl WaylandWindow {
|
||||||
}
|
}
|
||||||
base_surface.commit();
|
base_surface.commit();
|
||||||
|
|
||||||
// `wl_egl_window_create` rejects a non-positive extent, and a float-to-int cast turns
|
let wl_egl_surface =
|
||||||
// both a negative and a NaN into zero, so the requested size is floored before the
|
WlEglSurface::new(base_surface.id(), inner_size.x as i32, inner_size.y as i32).unwrap();
|
||||||
// call rather than allowed to panic an app at startup over a bad saved size.
|
|
||||||
let egl_w = (inner_size.x as i32).max(1);
|
|
||||||
let egl_h = (inner_size.y as i32).max(1);
|
|
||||||
let wl_egl_surface = match WlEglSurface::new(base_surface.id(), egl_w, egl_h) {
|
|
||||||
Ok(surface) => surface,
|
|
||||||
Err(e) => {
|
|
||||||
crate::error!("wl_egl_window_create failed at {egl_w}x{egl_h}: {e:?}");
|
|
||||||
WlEglSurface::new(base_surface.id(), 800, 600)
|
|
||||||
.expect("wl_egl_window_create failed at the fallback size too")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let egl_surface = unsafe {
|
let egl_surface = unsafe {
|
||||||
(opengl_cx.libegl.eglCreateWindowSurface.unwrap())(
|
(opengl_cx.libegl.eglCreateWindowSurface.unwrap())(
|
||||||
opengl_cx.egl_display,
|
opengl_cx.egl_display,
|
||||||
|
|
|
||||||
|
|
@ -550,12 +550,11 @@ impl X11Cx {
|
||||||
CxOsOp::CreateWindow(window_id) => {
|
CxOsOp::CreateWindow(window_id) => {
|
||||||
let gl_cx = cx.os.opengl_cx.as_ref().unwrap();
|
let gl_cx = cx.os.opengl_cx.as_ref().unwrap();
|
||||||
let window = &cx.windows[window_id];
|
let window = &cx.windows[window_id];
|
||||||
let (create_position, create_inner_size) = window.create_geom();
|
|
||||||
let opengl_window = OpenglWindow::new(
|
let opengl_window = OpenglWindow::new(
|
||||||
window_id,
|
window_id,
|
||||||
gl_cx,
|
gl_cx,
|
||||||
create_inner_size,
|
window.create_inner_size.unwrap_or(dvec2(800., 600.)),
|
||||||
create_position,
|
window.create_position,
|
||||||
&window.create_title,
|
&window.create_title,
|
||||||
&window.create_app_id,
|
&window.create_app_id,
|
||||||
window.is_fullscreen,
|
window.is_fullscreen,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod linux_x11;
|
pub mod linux_x11;
|
||||||
pub mod linux_x11_stdin;
|
pub mod linux_x11_stdin;
|
||||||
pub mod opengl_x11;
|
pub mod opengl_x11;
|
||||||
pub mod x11_screen;
|
|
||||||
pub mod x11_sys;
|
pub mod x11_sys;
|
||||||
pub mod xlib_app;
|
pub mod xlib_app;
|
||||||
pub mod xlib_event;
|
pub mod xlib_event;
|
||||||
|
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
//! Display geometry for the X11 backend.
|
|
||||||
|
|
||||||
use {
|
|
||||||
self::super::{x11_sys, xlib_app::get_xlib_app_global},
|
|
||||||
crate::{makepad_math::*, screen::ScreenGeom},
|
|
||||||
std::{
|
|
||||||
ffi::CString,
|
|
||||||
mem,
|
|
||||||
os::raw::{c_int, c_long, c_uchar, c_ulong},
|
|
||||||
ptr,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Reads a `CARDINAL` array property from the root window.
|
|
||||||
///
|
|
||||||
/// Returns an empty vector when the property is absent, which is the normal answer from a
|
|
||||||
/// window manager that does not implement the hint.
|
|
||||||
unsafe fn root_cardinals(name: &str) -> Vec<c_long> {
|
|
||||||
let display = get_xlib_app_global().display;
|
|
||||||
let Ok(name) = CString::new(name) else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
// `only_if_exists` = true: never define the atom, only look one up.
|
|
||||||
let atom = unsafe { x11_sys::XInternAtom(display, name.as_ptr(), 1) };
|
|
||||||
if atom == 0 {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
let root = unsafe {
|
|
||||||
let screen = x11_sys::XDefaultScreen(display);
|
|
||||||
x11_sys::XRootWindow(display, screen)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut actual_type: x11_sys::Atom = 0;
|
|
||||||
let mut actual_format: c_int = 0;
|
|
||||||
let mut n_items: c_ulong = 0;
|
|
||||||
let mut bytes_after: c_ulong = 0;
|
|
||||||
let mut data: *mut c_uchar = ptr::null_mut();
|
|
||||||
// A long_length of 64 covers 16 desktops' worth of four-value work areas; anything past
|
|
||||||
// that is left unread rather than paged in.
|
|
||||||
let status = unsafe {
|
|
||||||
x11_sys::XGetWindowProperty(
|
|
||||||
display,
|
|
||||||
root,
|
|
||||||
atom,
|
|
||||||
0,
|
|
||||||
64,
|
|
||||||
0,
|
|
||||||
x11_sys::AnyPropertyType as c_ulong,
|
|
||||||
&mut actual_type,
|
|
||||||
&mut actual_format,
|
|
||||||
&mut n_items,
|
|
||||||
&mut bytes_after,
|
|
||||||
&mut data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
// Xlib's `Success` is zero; the constant itself is not in the bindings.
|
|
||||||
if status != 0 || data.is_null() {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
// Xlib hands back 32-bit properties widened to `long`, whatever the wire format says.
|
|
||||||
let out = if actual_format == 32 {
|
|
||||||
unsafe { std::slice::from_raw_parts(data as *const c_long, n_items as usize).to_vec() }
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
unsafe { x11_sys::XFree(data as *mut _) };
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The X screen's full extent, from the root window's geometry.
|
|
||||||
unsafe fn root_bounds() -> Option<Rect> {
|
|
||||||
let display = get_xlib_app_global().display;
|
|
||||||
let root = unsafe {
|
|
||||||
let screen = x11_sys::XDefaultScreen(display);
|
|
||||||
x11_sys::XRootWindow(display, screen)
|
|
||||||
};
|
|
||||||
let mut xwa = mem::MaybeUninit::<x11_sys::XWindowAttributes>::uninit();
|
|
||||||
if unsafe { x11_sys::XGetWindowAttributes(display, root, xwa.as_mut_ptr()) } == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let xwa = unsafe { xwa.assume_init() };
|
|
||||||
if xwa.width <= 0 || xwa.height <= 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(Rect {
|
|
||||||
pos: dvec2(0.0, 0.0),
|
|
||||||
size: dvec2(xwa.width as f64, xwa.height as f64),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The desktop area a window may occupy, in physical pixels — the coordinate space
|
|
||||||
/// `XMoveWindow` and `XCreateWindow` take positions in.
|
|
||||||
///
|
|
||||||
/// This is one entry covering the whole X screen, not one per physical monitor: splitting a
|
|
||||||
/// Xinerama screen into its heads needs libXinerama or libXrandr, and makepad links neither.
|
|
||||||
/// It still keeps a window on the desktop and clear of the panels, which is what a restored
|
|
||||||
/// position can get wrong. The extent comes from the root window, and the reserved edges
|
|
||||||
/// from the EWMH `_NET_WORKAREA` hint of the current desktop, falling back to the full extent
|
|
||||||
/// under a window manager that publishes neither.
|
|
||||||
pub fn x11_screens() -> Vec<ScreenGeom> {
|
|
||||||
let Some(bounds) = (unsafe { root_bounds() }) else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
let desktop = unsafe { root_cardinals("_NET_CURRENT_DESKTOP") }
|
|
||||||
.first()
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(0)
|
|
||||||
.max(0) as usize;
|
|
||||||
let areas = unsafe { root_cardinals("_NET_WORKAREA") };
|
|
||||||
let work_area = areas
|
|
||||||
.chunks_exact(4)
|
|
||||||
.nth(desktop)
|
|
||||||
.or_else(|| areas.chunks_exact(4).next())
|
|
||||||
.map(|a| Rect {
|
|
||||||
pos: dvec2(a[0] as f64, a[1] as f64),
|
|
||||||
size: dvec2(a[2] as f64, a[3] as f64),
|
|
||||||
})
|
|
||||||
.filter(|r| r.size.x > 0.0 && r.size.y > 0.0)
|
|
||||||
.unwrap_or(bounds);
|
|
||||||
|
|
||||||
vec![ScreenGeom {
|
|
||||||
bounds,
|
|
||||||
work_area,
|
|
||||||
is_primary: true,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
use {
|
use {
|
||||||
self::super::{x11_sys, xlib_app::*, xlib_event::XlibEvent},
|
self::super::{x11_sys, xlib_app::*, xlib_event::XlibEvent},
|
||||||
crate::{
|
crate::{area::Area, cursor::MouseCursor, event::*, makepad_math::{Rect, Vec2d}, window::WindowId},
|
||||||
area::Area, cursor::MouseCursor, event::*,
|
|
||||||
makepad_math::{dvec2, Rect, Vec2d},
|
|
||||||
os::linux::x11::x11_screen::x11_screens,
|
|
||||||
screen::fit_window_rect_to_screens,
|
|
||||||
window::WindowId,
|
|
||||||
},
|
|
||||||
std::{
|
std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
ffi::{CStr, CString},
|
ffi::{CStr, CString},
|
||||||
|
|
@ -116,26 +110,22 @@ impl XlibWindow {
|
||||||
| x11_sys::LeaveWindowMask) as c_long;
|
| x11_sys::LeaveWindowMask) as c_long;
|
||||||
|
|
||||||
let dpi_factor = self.get_dpi_factor();
|
let dpi_factor = self.get_dpi_factor();
|
||||||
// A restored size and position are only as good as the desktop layout they were
|
|
||||||
// saved on, so the request is fitted before it reaches the server. Doing it here
|
|
||||||
// covers the geometry, the size hints and the pre-map move alike.
|
|
||||||
let (position, size) = fit_create_geom(position, size, dpi_factor);
|
|
||||||
// Create a window
|
// Create a window
|
||||||
// X11 encodes a window position as INT16 and an extent as CARD16, and a request
|
|
||||||
// outside those ranges is a BadValue protocol error — which, with no error handler
|
|
||||||
// installed, terminates the process. The fit above already keeps a placement on the
|
|
||||||
// desktop; these clamps are what guarantee the request is expressible at all.
|
|
||||||
let (create_x, create_y) = match position {
|
|
||||||
Some(position) => (clamp_coord(position.x), clamp_coord(position.y)),
|
|
||||||
None => (150, 60),
|
|
||||||
};
|
|
||||||
let window = x11_sys::XCreateWindow(
|
let window = x11_sys::XCreateWindow(
|
||||||
display,
|
display,
|
||||||
root_window,
|
root_window,
|
||||||
create_x,
|
if position.is_some() {
|
||||||
create_y,
|
position.unwrap().x
|
||||||
clamp_extent(size.x * dpi_factor),
|
} else {
|
||||||
clamp_extent(size.y * dpi_factor),
|
150.0
|
||||||
|
} as i32,
|
||||||
|
if position.is_some() {
|
||||||
|
position.unwrap().y
|
||||||
|
} else {
|
||||||
|
60.0
|
||||||
|
} as i32,
|
||||||
|
(size.x * dpi_factor) as u32,
|
||||||
|
(size.y * dpi_factor) as u32,
|
||||||
0,
|
0,
|
||||||
visual_info.depth,
|
visual_info.depth,
|
||||||
x11_sys::InputOutput as u32,
|
x11_sys::InputOutput as u32,
|
||||||
|
|
@ -748,8 +738,6 @@ impl XlibWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The window's top-left corner in physical screen pixels; see [`Self::set_position`]
|
|
||||||
/// for why positions are not scaled the way sizes are.
|
|
||||||
pub fn get_position(&self) -> Vec2d {
|
pub fn get_position(&self) -> Vec2d {
|
||||||
unsafe {
|
unsafe {
|
||||||
let display = get_xlib_app_global().display;
|
let display = get_xlib_app_global().display;
|
||||||
|
|
@ -805,29 +793,18 @@ impl XlibWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the window's top-left corner to `pos`, in physical screen pixels — the same
|
|
||||||
/// space [`Self::get_position`] reports and `XCreateWindow` takes, so
|
|
||||||
/// `set_position(get_position())` leaves the window where it is. Sizes are logical and
|
|
||||||
/// scale with the DPI; positions are not, because a screen coordinate on a multi-monitor
|
|
||||||
/// desktop has no single scale factor to be logical in.
|
|
||||||
pub fn set_position(&mut self, pos: Vec2d) {
|
pub fn set_position(&mut self, pos: Vec2d) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let display = get_xlib_app_global().display;
|
let display = get_xlib_app_global().display;
|
||||||
// A caller placing the window cannot know the desktop it is placing into, so the
|
let dpi_factor = self.get_dpi_factor();
|
||||||
// request is fitted to the desktop that is actually there.
|
|
||||||
let want = Rect {
|
|
||||||
pos,
|
|
||||||
size: self.get_outer_size(),
|
|
||||||
};
|
|
||||||
let fitted = fit_window_rect_to_screens(&x11_screens(), want);
|
|
||||||
x11_sys::XMoveWindow(
|
x11_sys::XMoveWindow(
|
||||||
display,
|
display,
|
||||||
self.window.unwrap(),
|
self.window.unwrap(),
|
||||||
clamp_coord(fitted.pos.x),
|
(pos.x * dpi_factor) as i32,
|
||||||
clamp_coord(fitted.pos.y),
|
(pos.y * dpi_factor) as i32,
|
||||||
);
|
);
|
||||||
x11_sys::XFlush(display);
|
x11_sys::XFlush(display);
|
||||||
self.last_window_geom.position = fitted.pos;
|
self.last_window_geom.position = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1307,48 +1284,3 @@ impl DndAtoms {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fits a requested window placement onto the desktop.
|
|
||||||
///
|
|
||||||
/// Takes and returns the pair `XCreateWindow` is called with: a position in physical pixels
|
|
||||||
/// and an inner size in logical pixels. `None` leaves placement to the window manager, which
|
|
||||||
/// already puts the window somewhere visible, so it passes straight through.
|
|
||||||
fn fit_create_geom(
|
|
||||||
position: Option<Vec2d>,
|
|
||||||
size: Vec2d,
|
|
||||||
dpi_factor: f64,
|
|
||||||
) -> (Option<Vec2d>, Vec2d) {
|
|
||||||
let Some(pos) = position else {
|
|
||||||
return (None, size);
|
|
||||||
};
|
|
||||||
let screens = x11_screens();
|
|
||||||
if screens.is_empty() {
|
|
||||||
return (position, size);
|
|
||||||
}
|
|
||||||
let want = Rect {
|
|
||||||
pos,
|
|
||||||
size: dvec2(size.x * dpi_factor, size.y * dpi_factor),
|
|
||||||
};
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, want);
|
|
||||||
(
|
|
||||||
Some(fitted.pos),
|
|
||||||
dvec2(fitted.size.x / dpi_factor, fitted.size.y / dpi_factor),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clamps a window coordinate into the INT16 range the X11 protocol encodes it in.
|
|
||||||
fn clamp_coord(v: f64) -> c_int {
|
|
||||||
if !v.is_finite() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
(v as i64).clamp(-32768, 32767) as c_int
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clamps a window extent into the CARD16 range the X11 protocol encodes it in. Zero is not
|
|
||||||
/// a legal extent, so the floor is one pixel.
|
|
||||||
fn clamp_extent(v: f64) -> u32 {
|
|
||||||
if !v.is_finite() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
(v as i64).clamp(1, 65535) as u32
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ pub mod video_file_decoder;
|
||||||
pub mod video_file_encoder;
|
pub mod video_file_encoder;
|
||||||
pub mod wasapi;
|
pub mod wasapi;
|
||||||
pub mod win32_event;
|
pub mod win32_event;
|
||||||
pub mod win32_screen;
|
|
||||||
pub mod win32_window;
|
pub mod win32_window;
|
||||||
pub mod windows_media;
|
pub mod windows_media;
|
||||||
pub mod windows_media_engine_notify;
|
pub mod windows_media_engine_notify;
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
//! Display enumeration for the Win32 backend.
|
|
||||||
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
makepad_math::*,
|
|
||||||
screen::ScreenGeom,
|
|
||||||
windows::Win32::{
|
|
||||||
Foundation::{LPARAM, RECT},
|
|
||||||
Graphics::Gdi::{HDC, HMONITOR},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
std::{mem::size_of, ptr},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// `MONITORINFO`, absent from the vendored `windows` bindings. `cb_size` tells
|
|
||||||
/// `GetMonitorInfoW` which layout it was handed, so it must be filled in before the call.
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, Default)]
|
|
||||||
struct MonitorInfo {
|
|
||||||
cb_size: u32,
|
|
||||||
rc_monitor: RECT,
|
|
||||||
rc_work: RECT,
|
|
||||||
dw_flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `MONITORINFOF_PRIMARY`: the display holding the origin of the virtual screen.
|
|
||||||
const MONITORINFOF_PRIMARY: u32 = 1;
|
|
||||||
|
|
||||||
type MonitorEnumProc =
|
|
||||||
unsafe extern "system" fn(HMONITOR, HDC, *mut RECT, LPARAM) -> windows_core::BOOL;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
unsafe fn EnumDisplayMonitors(
|
|
||||||
hdc: HDC,
|
|
||||||
clip: *const RECT,
|
|
||||||
callback: MonitorEnumProc,
|
|
||||||
data: LPARAM,
|
|
||||||
) -> windows_core::BOOL {
|
|
||||||
windows_core::link!("user32.dll" "system" fn EnumDisplayMonitors(hdc : HDC, clip : *const RECT, callback : MonitorEnumProc, data : LPARAM) -> windows_core::BOOL);
|
|
||||||
unsafe { EnumDisplayMonitors(hdc, clip, callback, data) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
unsafe fn GetMonitorInfoW(monitor: HMONITOR, info: *mut MonitorInfo) -> windows_core::BOOL {
|
|
||||||
windows_core::link!("user32.dll" "system" fn GetMonitorInfoW(monitor : HMONITOR, info : *mut MonitorInfo) -> windows_core::BOOL);
|
|
||||||
unsafe { GetMonitorInfoW(monitor, info) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a Win32 edge-addressed rectangle to the origin-plus-size form makepad uses.
|
|
||||||
fn rect_of(r: RECT) -> Rect {
|
|
||||||
Rect {
|
|
||||||
pos: dvec2(r.left as f64, r.top as f64),
|
|
||||||
size: dvec2((r.right - r.left) as f64, (r.bottom - r.top) as f64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The displays currently attached, in physical screen pixels — the coordinate space
|
|
||||||
/// `CreateWindowExW` and `MoveWindow` take window positions in.
|
|
||||||
pub fn win32_screens() -> Vec<ScreenGeom> {
|
|
||||||
unsafe extern "system" fn collect(
|
|
||||||
monitor: HMONITOR,
|
|
||||||
_hdc: HDC,
|
|
||||||
_clip: *mut RECT,
|
|
||||||
data: LPARAM,
|
|
||||||
) -> windows_core::BOOL {
|
|
||||||
let screens = unsafe { &mut *(data.0 as *mut Vec<ScreenGeom>) };
|
|
||||||
let mut info = MonitorInfo {
|
|
||||||
cb_size: size_of::<MonitorInfo>() as u32,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
if unsafe { GetMonitorInfoW(monitor, &mut info) }.as_bool() {
|
|
||||||
screens.push(ScreenGeom {
|
|
||||||
bounds: rect_of(info.rc_monitor),
|
|
||||||
work_area: rect_of(info.rc_work),
|
|
||||||
is_primary: info.dw_flags & MONITORINFOF_PRIMARY != 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Keep enumerating; a display whose info could not be read is simply skipped.
|
|
||||||
windows_core::BOOL(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut screens = Vec::new();
|
|
||||||
unsafe {
|
|
||||||
let _ = EnumDisplayMonitors(
|
|
||||||
HDC::default(),
|
|
||||||
ptr::null(),
|
|
||||||
collect,
|
|
||||||
LPARAM(&mut screens as *mut Vec<ScreenGeom> as isize),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
screens
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a rectangle in Win32 "workspace" coordinates to screen coordinates.
|
|
||||||
///
|
|
||||||
/// `WINDOWPLACEMENT` reports a normal top-level window in workspace coordinates: screen
|
|
||||||
/// coordinates shifted by the primary display's reserved edges. The two spaces coincide for
|
|
||||||
/// the usual bottom-docked taskbar and differ by its thickness when it sits at the top or on
|
|
||||||
/// the left, so the shift is read from the primary display rather than assumed to be zero.
|
|
||||||
pub fn workspace_rect_to_screen(r: RECT) -> RECT {
|
|
||||||
let Some(primary) = win32_screens().into_iter().find(|s| s.is_primary) else {
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
let dx = (primary.work_area.pos.x - primary.bounds.pos.x) as i32;
|
|
||||||
let dy = (primary.work_area.pos.y - primary.bounds.pos.y) as i32;
|
|
||||||
RECT {
|
|
||||||
left: r.left + dx,
|
|
||||||
top: r.top + dy,
|
|
||||||
right: r.right + dx,
|
|
||||||
bottom: r.bottom + dy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,9 +10,7 @@ use {
|
||||||
droptarget::*,
|
droptarget::*,
|
||||||
win32_app::{encode_wide, with_win32_app, Win32App},
|
win32_app::{encode_wide, with_win32_app, Win32App},
|
||||||
win32_event::*,
|
win32_event::*,
|
||||||
win32_screen::{win32_screens, workspace_rect_to_screen},
|
|
||||||
},
|
},
|
||||||
screen::{clamp_point_to_screens, fit_window_rect_to_screens},
|
|
||||||
window::{WindowBackdrop, WindowId, WindowVisuals},
|
window::{WindowBackdrop, WindowId, WindowVisuals},
|
||||||
windows::{
|
windows::{
|
||||||
core::PCWSTR,
|
core::PCWSTR,
|
||||||
|
|
@ -90,7 +88,6 @@ use {
|
||||||
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE,
|
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE,
|
||||||
WM_MOUSEWHEEL, WM_NCCALCSIZE, WM_NCHITTEST, WM_RBUTTONDOWN, WM_RBUTTONUP,
|
WM_MOUSEWHEEL, WM_NCCALCSIZE, WM_NCHITTEST, WM_RBUTTONDOWN, WM_RBUTTONUP,
|
||||||
WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP,
|
WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP,
|
||||||
GetWindowPlacement, WINDOWPLACEMENT,
|
|
||||||
WS_BORDER, WS_CAPTION, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES,
|
WS_BORDER, WS_CAPTION, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES,
|
||||||
WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_TOOLWINDOW, WS_EX_TOPMOST,
|
WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_TOOLWINDOW, WS_EX_TOPMOST,
|
||||||
WS_EX_WINDOWEDGE, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_THICKFRAME,
|
WS_EX_WINDOWEDGE, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_THICKFRAME,
|
||||||
|
|
@ -111,13 +108,6 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Whether a screen coordinate survives the conversion `CreateWindowExW` and `MoveWindow`
|
|
||||||
/// take: a real number inside `i32`, and not the `CW_USEDEFAULT` sentinel that `i32::MIN`
|
|
||||||
/// would be read as.
|
|
||||||
fn is_placeable(v: f64) -> bool {
|
|
||||||
v.is_finite() && v > i32::MIN as f64 && v < i32::MAX as f64
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct AccentPolicy {
|
struct AccentPolicy {
|
||||||
accent_state: u32,
|
accent_state: u32,
|
||||||
|
|
@ -213,11 +203,6 @@ pub struct Win32Window {
|
||||||
/// Set by `close_window()`; suppresses the WM_ACTIVATE-derived
|
/// Set by `close_window()`; suppresses the WM_ACTIVATE-derived
|
||||||
/// `PopupDismissed(FocusLost)`, which would duplicate the closer's dismissal.
|
/// `PopupDismissed(FocusLost)`, which would duplicate the closer's dismissal.
|
||||||
pub is_closing: Cell<bool>,
|
pub is_closing: Cell<bool>,
|
||||||
/// Whether the window is inside the system's modal move/size loop, i.e. the user is
|
|
||||||
/// dragging it. `WM_MOVE` arrives per mouse step there, so the position is published once
|
|
||||||
/// on the way out rather than on every step; a programmatic move, which sets no such
|
|
||||||
/// state, publishes immediately.
|
|
||||||
pub in_size_move: Cell<bool>,
|
|
||||||
pub ignore_wmsize: usize,
|
pub ignore_wmsize: usize,
|
||||||
pub hwnd: HWND,
|
pub hwnd: HWND,
|
||||||
pub track_mouse_event: bool,
|
pub track_mouse_event: bool,
|
||||||
|
|
@ -533,22 +518,10 @@ impl Win32Window {
|
||||||
|
|
||||||
let style_ex = WS_EX_WINDOWEDGE | WS_EX_APPWINDOW | WS_EX_ACCEPTFILES;
|
let style_ex = WS_EX_WINDOWEDGE | WS_EX_APPWINDOW | WS_EX_ACCEPTFILES;
|
||||||
|
|
||||||
let (x, y) = match position {
|
let (x, y) = if let Some(position) = position {
|
||||||
// A restored position can name a display that is gone, or hold values no display
|
(position.x as i32, position.y as i32)
|
||||||
// ever had. Pinning it now keeps `CreateWindowExW` and the sizing that follows
|
|
||||||
// working on real coordinates; `init` fits the finished rectangle once the size is
|
|
||||||
// known. A coordinate still out of range after pinning means no display could be
|
|
||||||
// enumerated, so the system's own placement is used instead of a value that would
|
|
||||||
// saturate on the way to the API.
|
|
||||||
Some(position) => {
|
|
||||||
let pinned = clamp_point_to_screens(&win32_screens(), position);
|
|
||||||
if is_placeable(pinned.x) && is_placeable(pinned.y) {
|
|
||||||
(pinned.x as i32, pinned.y as i32)
|
|
||||||
} else {
|
} else {
|
||||||
(CW_USEDEFAULT, CW_USEDEFAULT)
|
(CW_USEDEFAULT, CW_USEDEFAULT)
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (CW_USEDEFAULT, CW_USEDEFAULT),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let hwnd = unsafe {
|
let hwnd = unsafe {
|
||||||
|
|
@ -594,7 +567,6 @@ impl Win32Window {
|
||||||
nc_dq_gen: Cell::new(0),
|
nc_dq_gen: Cell::new(0),
|
||||||
geom_event_gen: Cell::new(0),
|
geom_event_gen: Cell::new(0),
|
||||||
is_closing: Cell::new(false),
|
is_closing: Cell::new(false),
|
||||||
in_size_move: Cell::new(false),
|
|
||||||
ignore_wmsize: 0,
|
ignore_wmsize: 0,
|
||||||
hwnd,
|
hwnd,
|
||||||
track_mouse_event: false,
|
track_mouse_event: false,
|
||||||
|
|
@ -649,7 +621,6 @@ impl Win32Window {
|
||||||
nc_dq_gen: Cell::new(0),
|
nc_dq_gen: Cell::new(0),
|
||||||
geom_event_gen: Cell::new(0),
|
geom_event_gen: Cell::new(0),
|
||||||
is_closing: Cell::new(false),
|
is_closing: Cell::new(false),
|
||||||
in_size_move: Cell::new(false),
|
|
||||||
ignore_wmsize: 0,
|
ignore_wmsize: 0,
|
||||||
hwnd,
|
hwnd,
|
||||||
track_mouse_event: false,
|
track_mouse_event: false,
|
||||||
|
|
@ -678,50 +649,6 @@ impl Win32Window {
|
||||||
self.set_inner_size(size);
|
self.set_inner_size(size);
|
||||||
if self.is_fullscreen {
|
if self.is_fullscreen {
|
||||||
self.maximize();
|
self.maximize();
|
||||||
} else if !self.is_popup {
|
|
||||||
// A restored size and position are only as good as the display layout they were
|
|
||||||
// saved on. Popups are placed against their parent and left alone; a maximized
|
|
||||||
// window is the system's to place.
|
|
||||||
self.fit_to_screens();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Moves and resizes the window so it sits entirely within one display's work area.
|
|
||||||
///
|
|
||||||
/// See `crate::screen::fit_window_rect_to_screens` for what counts as a fit and why it
|
|
||||||
/// is unconditional. A window rectangle that already fits is left untouched, so this
|
|
||||||
/// costs one `GetWindowRect` and a display enumeration in the common case.
|
|
||||||
pub fn fit_to_screens(&mut self) {
|
|
||||||
let screens = win32_screens();
|
|
||||||
if screens.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut rect = RECT::default();
|
|
||||||
if unsafe { GetWindowRect(self.hwnd, &mut rect) }.is_err() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let current = Rect {
|
|
||||||
pos: dvec2(rect.left as f64, rect.top as f64),
|
|
||||||
size: dvec2(
|
|
||||||
(rect.right - rect.left) as f64,
|
|
||||||
(rect.bottom - rect.top) as f64,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, current);
|
|
||||||
if fitted == current {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Err(e) = unsafe {
|
|
||||||
MoveWindow(
|
|
||||||
self.hwnd,
|
|
||||||
fitted.pos.x as i32,
|
|
||||||
fitted.pos.y as i32,
|
|
||||||
fitted.size.x as i32,
|
|
||||||
fitted.size.y as i32,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
} {
|
|
||||||
crate::error!("Fitting the window into the visible screen area failed: {}", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1087,25 +1014,12 @@ impl Win32Window {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
WM_ENTERSIZEMOVE => {
|
WM_ENTERSIZEMOVE => {
|
||||||
window.in_size_move.set(true);
|
|
||||||
with_win32_app(|app| app.start_resize());
|
with_win32_app(|app| app.start_resize());
|
||||||
window.do_callback(Win32Event::WindowResizeLoopStart(window.window_id));
|
window.do_callback(Win32Event::WindowResizeLoopStart(window.window_id));
|
||||||
}
|
}
|
||||||
// WM_CANCELMODE (0x001F): the system is telling the window to abandon any internal
|
|
||||||
// mode it is in. DefWindowProc normally still leaves the move/size loop through
|
|
||||||
// WM_EXITSIZEMOVE, so this is a failsafe: `in_size_move` is the only thing gating
|
|
||||||
// position publication, and a stuck `true` would silently stop it for the window's
|
|
||||||
// lifetime.
|
|
||||||
0x001F => {
|
|
||||||
window.in_size_move.set(false);
|
|
||||||
}
|
|
||||||
WM_EXITSIZEMOVE => {
|
WM_EXITSIZEMOVE => {
|
||||||
window.in_size_move.set(false);
|
|
||||||
with_win32_app(|app| app.stop_resize());
|
with_win32_app(|app| app.stop_resize());
|
||||||
window.do_callback(Win32Event::WindowResizeLoopStop(window.window_id));
|
window.do_callback(Win32Event::WindowResizeLoopStop(window.window_id));
|
||||||
// A drag that only moved the window produced no WM_SIZE, so this is the one
|
|
||||||
// chance to publish where it ended up.
|
|
||||||
window.send_move_event();
|
|
||||||
}
|
}
|
||||||
// WM_SIZING (0x0214) fires BEFORE the window is resized with
|
// WM_SIZING (0x0214) fires BEFORE the window is resized with
|
||||||
// the proposed new rect. By pre-rendering at this size, the
|
// the proposed new rect. By pre-rendering at this size, the
|
||||||
|
|
@ -1119,14 +1033,6 @@ impl Win32Window {
|
||||||
// The window may have moved to a monitor with a different scale; drop the cached
|
// The window may have moved to a monitor with a different scale; drop the cached
|
||||||
// DPI so send_change_event() (and subsequent hit-tests) re-read the new value.
|
// DPI so send_change_event() (and subsequent hit-tests) re-read the new value.
|
||||||
window.invalidate_cached_dpi();
|
window.invalidate_cached_dpi();
|
||||||
// Minimizing does not change the window's geometry, it parks it. Publishing the
|
|
||||||
// iconic rect would relayout the whole UI at zero size and poison whatever the
|
|
||||||
// app persists; `outer_rect` already answers from the restored placement, so
|
|
||||||
// there is nothing here worth reporting either.
|
|
||||||
const SIZE_MINIMIZED: usize = 1;
|
|
||||||
if wparam.0 == SIZE_MINIMIZED {
|
|
||||||
return LRESULT(0);
|
|
||||||
}
|
|
||||||
window.send_change_event();
|
window.send_change_event();
|
||||||
}
|
}
|
||||||
WM_DPICHANGED => {
|
WM_DPICHANGED => {
|
||||||
|
|
@ -1165,13 +1071,6 @@ impl Win32Window {
|
||||||
0x0003 => {
|
0x0003 => {
|
||||||
window.nc_dq_cache.set(None);
|
window.nc_dq_cache.set(None);
|
||||||
window.nc_dq_gen.set(window.nc_dq_gen.get().wrapping_add(1));
|
window.nc_dq_gen.set(window.nc_dq_gen.get().wrapping_add(1));
|
||||||
// Publish the new position, or the window keeps reporting — and the app keeps
|
|
||||||
// persisting — where it used to be. A user drag is left to WM_EXITSIZEMOVE:
|
|
||||||
// this message arrives per mouse step, and each published geometry costs a
|
|
||||||
// full redraw on the Cx side.
|
|
||||||
if !window.in_size_move.get() {
|
|
||||||
window.send_move_event();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
WM_CLOSE => {
|
WM_CLOSE => {
|
||||||
// close requested
|
// close requested
|
||||||
|
|
@ -1471,51 +1370,31 @@ impl Win32Window {
|
||||||
self.ime_rect = rect;
|
self.ime_rect = rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The window's outer rectangle in screen pixels, answered from the restored placement
|
|
||||||
/// while the window is minimized.
|
|
||||||
///
|
|
||||||
/// A minimized window has no on-screen rectangle: `GetWindowRect` reports the off-screen
|
|
||||||
/// parking position `(-32000, -32000)` and `GetClientRect` a zero size. An app that
|
|
||||||
/// persists its geometry on shutdown would save those and restore, next launch, a window
|
|
||||||
/// it can neither see nor grab — so the restored placement the system keeps for exactly
|
|
||||||
/// this purpose is reported instead.
|
|
||||||
fn outer_rect(&self) -> RECT {
|
|
||||||
unsafe {
|
|
||||||
if self.is_iconic() {
|
|
||||||
let mut placement = WINDOWPLACEMENT {
|
|
||||||
length: mem::size_of::<WINDOWPLACEMENT>() as u32,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
if GetWindowPlacement(self.hwnd, &mut placement).is_ok() {
|
|
||||||
return workspace_rect_to_screen(placement.rcNormalPosition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut rect = RECT::default();
|
|
||||||
GetWindowRect(self.hwnd, &mut rect).unwrap();
|
|
||||||
rect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The window's top-left corner in physical screen pixels; see [`Self::set_position`]
|
|
||||||
/// for why positions are not scaled the way sizes are.
|
|
||||||
pub fn get_position(&self) -> Vec2d {
|
pub fn get_position(&self) -> Vec2d {
|
||||||
let rect = self.outer_rect();
|
unsafe {
|
||||||
|
let mut rect = RECT {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
};
|
||||||
|
GetWindowRect(self.hwnd, &mut rect).unwrap();
|
||||||
Vec2d {
|
Vec2d {
|
||||||
x: rect.left as f64,
|
x: rect.left as f64,
|
||||||
y: rect.top as f64,
|
y: rect.top as f64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_inner_size(&self) -> Vec2d {
|
pub fn get_inner_size(&self) -> Vec2d {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut rect = RECT::default();
|
let mut rect = RECT {
|
||||||
if self.is_iconic() {
|
left: 0,
|
||||||
// A restored window of this backend is fully client-sized (see the
|
top: 0,
|
||||||
// `WM_NCCALCSIZE` handler), so its outer rectangle is also its client size.
|
bottom: 0,
|
||||||
rect = self.outer_rect();
|
right: 0,
|
||||||
} else {
|
};
|
||||||
GetClientRect(self.hwnd, &mut rect).unwrap();
|
GetClientRect(self.hwnd, &mut rect).unwrap();
|
||||||
}
|
|
||||||
let dpi = self.get_dpi_factor();
|
let dpi = self.get_dpi_factor();
|
||||||
Vec2d {
|
Vec2d {
|
||||||
x: (rect.right - rect.left) as f64 / dpi,
|
x: (rect.right - rect.left) as f64 / dpi,
|
||||||
|
|
@ -1525,19 +1404,22 @@ impl Win32Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_outer_size(&self) -> Vec2d {
|
pub fn get_outer_size(&self) -> Vec2d {
|
||||||
let rect = self.outer_rect();
|
unsafe {
|
||||||
|
let mut rect = RECT {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
};
|
||||||
|
GetWindowRect(self.hwnd, &mut rect).unwrap();
|
||||||
let dpi = self.get_dpi_factor();
|
let dpi = self.get_dpi_factor();
|
||||||
Vec2d {
|
Vec2d {
|
||||||
x: (rect.right - rect.left) as f64 / dpi,
|
x: (rect.right - rect.left) as f64 / dpi,
|
||||||
y: (rect.bottom - rect.top) as f64 / dpi,
|
y: (rect.bottom - rect.top) as f64 / dpi,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves the window's top-left corner to `pos`, in physical screen pixels — the same
|
|
||||||
/// space [`Self::get_position`] reports and `CreateWindowExW` takes, so
|
|
||||||
/// `set_position(get_position())` leaves the window where it is. Sizes are logical and
|
|
||||||
/// scale with the DPI; positions are not, because a screen coordinate on a multi-monitor
|
|
||||||
/// desktop has no single scale factor to be logical in.
|
|
||||||
pub fn set_position(&mut self, pos: Vec2d) {
|
pub fn set_position(&mut self, pos: Vec2d) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut window_rect = RECT {
|
let mut window_rect = RECT {
|
||||||
|
|
@ -1547,23 +1429,13 @@ impl Win32Window {
|
||||||
right: 0,
|
right: 0,
|
||||||
};
|
};
|
||||||
GetWindowRect(self.hwnd, &mut window_rect).unwrap();
|
GetWindowRect(self.hwnd, &mut window_rect).unwrap();
|
||||||
// A caller placing the window — restoring a saved position, cascading a new
|
let dpi = self.get_dpi_factor();
|
||||||
// window — cannot know the display layout it is placing into, so the request is
|
|
||||||
// fitted to the displays that are actually attached.
|
|
||||||
let want = Rect {
|
|
||||||
pos,
|
|
||||||
size: dvec2(
|
|
||||||
(window_rect.right - window_rect.left) as f64,
|
|
||||||
(window_rect.bottom - window_rect.top) as f64,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let fitted = fit_window_rect_to_screens(&win32_screens(), want);
|
|
||||||
MoveWindow(
|
MoveWindow(
|
||||||
self.hwnd,
|
self.hwnd,
|
||||||
fitted.pos.x as i32,
|
(pos.x * dpi) as i32,
|
||||||
fitted.pos.y as i32,
|
(pos.y * dpi) as i32,
|
||||||
fitted.size.x as i32,
|
window_rect.right - window_rect.left,
|
||||||
fitted.size.y as i32,
|
window_rect.bottom - window_rect.top,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -1723,27 +1595,6 @@ impl Win32Window {
|
||||||
Win32App::do_callback(event);
|
Win32App::do_callback(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publishes a position-only geometry change.
|
|
||||||
///
|
|
||||||
/// Moving a window does not change what it draws, so unlike [`Self::send_change_event`]
|
|
||||||
/// this asks for no repaint; it only keeps the published geometry — which is what an app
|
|
||||||
/// persists — in step with where the window actually is. Nothing is dispatched when the
|
|
||||||
/// geometry is unchanged, which is also what makes this safe to call for a minimize,
|
|
||||||
/// where `outer_rect` keeps answering from the restored placement.
|
|
||||||
pub fn send_move_event(&mut self) {
|
|
||||||
let new_geom = self.get_window_geom();
|
|
||||||
if new_geom == self.last_window_geom {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let old_geom = std::mem::replace(&mut self.last_window_geom, new_geom.clone());
|
|
||||||
self.geom_event_gen.set(self.geom_event_gen.get().wrapping_add(1));
|
|
||||||
self.do_callback(Win32Event::WindowGeomChange(WindowGeomChangeEvent {
|
|
||||||
window_id: self.window_id,
|
|
||||||
old_geom,
|
|
||||||
new_geom,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_change_event(&mut self) {
|
pub fn send_change_event(&mut self) {
|
||||||
// Record that a geometry event is published (see `geom_event_gen`).
|
// Record that a geometry event is published (see `geom_event_gen`).
|
||||||
self.geom_event_gen.set(self.geom_event_gen.get().wrapping_add(1));
|
self.geom_event_gen.set(self.geom_event_gen.get().wrapping_add(1));
|
||||||
|
|
|
||||||
|
|
@ -758,12 +758,11 @@ impl Cx {
|
||||||
match op {
|
match op {
|
||||||
CxOsOp::CreateWindow(window_id) => {
|
CxOsOp::CreateWindow(window_id) => {
|
||||||
let window = &mut self.windows[window_id];
|
let window = &mut self.windows[window_id];
|
||||||
let (create_position, create_inner_size) = window.create_geom();
|
|
||||||
let d3d11_window = D3d11Window::new(
|
let d3d11_window = D3d11Window::new(
|
||||||
window_id,
|
window_id,
|
||||||
&d3d11_cx,
|
&d3d11_cx,
|
||||||
create_inner_size,
|
window.create_inner_size.unwrap_or(dvec2(800., 600.)),
|
||||||
create_position,
|
window.create_position,
|
||||||
&window.create_title,
|
&window.create_title,
|
||||||
window.is_fullscreen,
|
window.is_fullscreen,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,497 +0,0 @@
|
||||||
//! Display geometry, and the policy that keeps a window inside it.
|
|
||||||
|
|
||||||
use crate::makepad_math::*;
|
|
||||||
|
|
||||||
/// The smallest window extent a fit ever produces. Small enough to leave a deliberately
|
|
||||||
/// compact tool window alone, large enough that the window still has a title bar to grab.
|
|
||||||
pub const MIN_WINDOW_SIZE: Vec2d = Vec2d { x: 200.0, y: 120.0 };
|
|
||||||
|
|
||||||
/// One display attached to the system.
|
|
||||||
///
|
|
||||||
/// The rectangles are in the same coordinate space as the platform's window-position API,
|
|
||||||
/// so a backend must build them from the same system calls it positions windows with:
|
|
||||||
/// physical pixels with a top-left origin on Windows and X11, points with Cocoa's
|
|
||||||
/// bottom-left origin on macOS.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct ScreenGeom {
|
|
||||||
/// The display's full extent.
|
|
||||||
pub bounds: Rect,
|
|
||||||
/// The extent left over once the system reserves its own space — the Windows taskbar,
|
|
||||||
/// the macOS menu bar and Dock, X11 struts. Windows are placed inside this.
|
|
||||||
pub work_area: Rect,
|
|
||||||
/// Whether this is the system's primary display.
|
|
||||||
pub is_primary: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Area shared by two rectangles; zero when they do not overlap.
|
|
||||||
fn overlap_area(a: Rect, b: Rect) -> f64 {
|
|
||||||
let w = (a.pos.x + a.size.x).min(b.pos.x + b.size.x) - a.pos.x.max(b.pos.x);
|
|
||||||
let h = (a.pos.y + a.size.y).min(b.pos.y + b.size.y) - a.pos.y.max(b.pos.y);
|
|
||||||
if w <= 0.0 || h <= 0.0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
w * h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Squared distance between two rectangles' centres.
|
|
||||||
fn center_distance_sq(a: Rect, b: Rect) -> f64 {
|
|
||||||
let d = a.center() - b.center();
|
|
||||||
d.x * d.x + d.y * d.y
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The parts of `a` that `b` does not cover, as up to four rectangles.
|
|
||||||
fn subtract(a: Rect, b: Rect) -> Vec<Rect> {
|
|
||||||
if overlap_area(a, b) <= 0.0 {
|
|
||||||
return vec![a];
|
|
||||||
}
|
|
||||||
let (ax0, ay0) = (a.pos.x, a.pos.y);
|
|
||||||
let (ax1, ay1) = (a.pos.x + a.size.x, a.pos.y + a.size.y);
|
|
||||||
let bx0 = b.pos.x.max(ax0);
|
|
||||||
let by0 = b.pos.y.max(ay0);
|
|
||||||
let bx1 = (b.pos.x + b.size.x).min(ax1);
|
|
||||||
let by1 = (b.pos.y + b.size.y).min(ay1);
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut push = |x0: f64, y0: f64, x1: f64, y1: f64| {
|
|
||||||
if x1 > x0 && y1 > y0 {
|
|
||||||
out.push(Rect {
|
|
||||||
pos: dvec2(x0, y0),
|
|
||||||
size: dvec2(x1 - x0, y1 - y0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
push(ax0, ay0, ax1, by0);
|
|
||||||
push(ax0, by1, ax1, ay1);
|
|
||||||
push(ax0, by0, bx0, by1);
|
|
||||||
push(bx1, by0, ax1, by1);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether `r` lies entirely within the union of `areas`, which may be several displays
|
|
||||||
/// covering it between them.
|
|
||||||
fn is_covered_by(areas: &[Rect], r: Rect) -> bool {
|
|
||||||
if !is_usable(r) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let mut remaining = vec![r];
|
|
||||||
for area in areas {
|
|
||||||
let mut next = Vec::new();
|
|
||||||
for piece in remaining.drain(..) {
|
|
||||||
next.extend(subtract(piece, *area));
|
|
||||||
}
|
|
||||||
if next.is_empty() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
remaining = next;
|
|
||||||
}
|
|
||||||
remaining.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether a rectangle is usable as a destination: real numbers, and some area to put a
|
|
||||||
/// window in.
|
|
||||||
fn is_usable(r: Rect) -> bool {
|
|
||||||
r.pos.x.is_finite()
|
|
||||||
&& r.pos.y.is_finite()
|
|
||||||
&& r.size.x.is_finite()
|
|
||||||
&& r.size.y.is_finite()
|
|
||||||
&& r.size.x > 0.0
|
|
||||||
&& r.size.y > 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fits a window's outer rectangle inside the work area of the display it belongs to.
|
|
||||||
///
|
|
||||||
/// Every window position an app restores has to survive a display layout that may have
|
|
||||||
/// changed completely since it was written: the display the window sat on can be gone, a
|
|
||||||
/// docked laptop can be back on a smaller built-in panel, and a state file saved while the
|
|
||||||
/// window was minimized holds coordinates no display ever had — Win32 reports position
|
|
||||||
/// `(-32000, -32000)` and a zero-sized client area for a minimized window, and an app that
|
|
||||||
/// persists that on shutdown restores a window it cannot see or grab on the next launch,
|
|
||||||
/// with no way back short of deleting the file. Fitting therefore applies to every
|
|
||||||
/// placement rather than only to values that look wrong.
|
|
||||||
///
|
|
||||||
/// A window that is already wholly on the desktop is returned untouched, including one
|
|
||||||
/// deliberately spanning two adjacent displays — the point is to rescue placements that
|
|
||||||
/// cannot be reached, not to enforce one window per display. Anything else moves onto the
|
|
||||||
/// display it overlaps most, or, when it overlaps none, the display nearest its centre; its
|
|
||||||
/// size is capped to that work area and floored at [`MIN_WINDOW_SIZE`], and its position is
|
|
||||||
/// pulled in until the whole window is visible.
|
|
||||||
///
|
|
||||||
/// An empty `screens` means the backend cannot enumerate displays — Wayland, where a client
|
|
||||||
/// is not allowed to know or choose where its windows go — and `window` is returned as-is.
|
|
||||||
pub fn fit_window_rect_to_screens(screens: &[ScreenGeom], window: Rect) -> Rect {
|
|
||||||
let usable: Vec<Rect> = screens
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.work_area)
|
|
||||||
.filter(|r| is_usable(*r))
|
|
||||||
.collect();
|
|
||||||
let Some(&first) = usable.first() else {
|
|
||||||
return window;
|
|
||||||
};
|
|
||||||
let primary = screens
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.is_primary && is_usable(s.work_area))
|
|
||||||
.map_or(first, |s| s.work_area);
|
|
||||||
|
|
||||||
// Coordinates that are not real numbers cannot be compared or clamped, so they name no
|
|
||||||
// display and get the primary's geometry to start from.
|
|
||||||
let mut want = window;
|
|
||||||
if !want.size.x.is_finite() || !want.size.y.is_finite() {
|
|
||||||
want.size = primary.size * 0.5;
|
|
||||||
}
|
|
||||||
if !want.pos.x.is_finite() || !want.pos.y.is_finite() {
|
|
||||||
want.pos = primary.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A window already wholly on the desktop is left exactly where it is, including one
|
|
||||||
// deliberately spanning two adjacent displays. Fitting exists to rescue a placement that
|
|
||||||
// cannot be reached, not to enforce one window per display.
|
|
||||||
if is_covered_by(&usable, want) {
|
|
||||||
return want;
|
|
||||||
}
|
|
||||||
|
|
||||||
let area = usable
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.max_by(|a, b| {
|
|
||||||
let (oa, ob) = (overlap_area(*a, want), overlap_area(*b, want));
|
|
||||||
oa.total_cmp(&ob).then_with(|| {
|
|
||||||
// No overlap anywhere leaves every candidate tied at zero; nearest centre
|
|
||||||
// breaks the tie, so a window off the right edge lands on the right display.
|
|
||||||
center_distance_sq(*b, want).total_cmp(¢er_distance_sq(*a, want))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or(primary);
|
|
||||||
|
|
||||||
let size = dvec2(
|
|
||||||
want.size.x.clamp(MIN_WINDOW_SIZE.x.min(area.size.x), area.size.x),
|
|
||||||
want.size.y.clamp(MIN_WINDOW_SIZE.y.min(area.size.y), area.size.y),
|
|
||||||
);
|
|
||||||
let pos = dvec2(
|
|
||||||
want.pos.x.clamp(area.pos.x, area.pos.x + area.size.x - size.x),
|
|
||||||
want.pos.y.clamp(area.pos.y, area.pos.y + area.size.y - size.y),
|
|
||||||
);
|
|
||||||
Rect { pos, size }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clamps a point into the work area of the display nearest to it, leaving room for a
|
|
||||||
/// window of at least [`MIN_WINDOW_SIZE`] to be visible from there.
|
|
||||||
///
|
|
||||||
/// A window origin can break creation on its own, before there is a finished rectangle to
|
|
||||||
/// fit: a coordinate out of the platform's integer range saturates when it reaches the
|
|
||||||
/// system call, and the sizing that follows is done relative to wherever the window landed.
|
|
||||||
/// Backends pin the origin through here first and fit the finished rectangle afterwards.
|
|
||||||
///
|
|
||||||
/// An empty `screens` returns the point unchanged, for the same reason
|
|
||||||
/// [`fit_window_rect_to_screens`] does.
|
|
||||||
pub fn clamp_point_to_screens(screens: &[ScreenGeom], point: Vec2d) -> Vec2d {
|
|
||||||
fit_window_rect_to_screens(
|
|
||||||
screens,
|
|
||||||
Rect {
|
|
||||||
pos: point,
|
|
||||||
size: dvec2(0.0, 0.0),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size a window falls back to when the requested one carries no usable information.
|
|
||||||
pub const DEFAULT_WINDOW_SIZE: Vec2d = Vec2d { x: 800.0, y: 600.0 };
|
|
||||||
|
|
||||||
/// Reduces a requested window size and position to values a windowing system can act on,
|
|
||||||
/// without needing to know anything about the attached displays.
|
|
||||||
///
|
|
||||||
/// This is the guard that has to hold everywhere, including the backends
|
|
||||||
/// [`fit_window_rect_to_screens`] cannot help: Wayland enumerates no displays for a client
|
|
||||||
/// and passes the size straight to `wl_egl_window_create`, which rejects a non-positive one;
|
|
||||||
/// X11 encodes width and height as unsigned 16-bit and answers a zero with a protocol error
|
|
||||||
/// that terminates the process by default. A saved `0`, a negative, or a `NaN` — all of which
|
|
||||||
/// a JSON state file can hold, and which `as i32` quietly turns into `0` — must therefore
|
|
||||||
/// never leave this function. Position is dropped rather than corrected when it is not a real
|
|
||||||
/// number: `None` means "the system places this window", which is always a safe answer.
|
|
||||||
pub fn sanitize_window_geom(position: Option<Vec2d>, size: Vec2d) -> (Option<Vec2d>, Vec2d) {
|
|
||||||
// A non-positive extent carries no information about how big the window should be — it is
|
|
||||||
// what a zeroed, truncated or minimized-window state file holds — so it gets the default
|
|
||||||
// rather than the floor, which would restore a technically-visible 200x120 sliver. A small
|
|
||||||
// positive size is a real request and is only raised to something grabbable.
|
|
||||||
let size = if size.x.is_finite() && size.y.is_finite() && size.x > 0.0 && size.y > 0.0 {
|
|
||||||
dvec2(
|
|
||||||
size.x.max(MIN_WINDOW_SIZE.x),
|
|
||||||
size.y.max(MIN_WINDOW_SIZE.y),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
DEFAULT_WINDOW_SIZE
|
|
||||||
};
|
|
||||||
let position = position.filter(|p| p.x.is_finite() && p.y.is_finite());
|
|
||||||
(position, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn screen(x: f64, y: f64, w: f64, h: f64, is_primary: bool) -> ScreenGeom {
|
|
||||||
let bounds = rect(x, y, w, h);
|
|
||||||
ScreenGeom {
|
|
||||||
bounds,
|
|
||||||
work_area: bounds,
|
|
||||||
is_primary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rect(x: f64, y: f64, w: f64, h: f64) -> Rect {
|
|
||||||
Rect {
|
|
||||||
pos: dvec2(x, y),
|
|
||||||
size: dvec2(w, h),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_already_inside_a_display_is_left_alone() {
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
let want = rect(100.0, 100.0, 800.0, 600.0);
|
|
||||||
assert_eq!(fit_window_rect_to_screens(&screens, want), want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_screens_leaves_the_request_untouched() {
|
|
||||||
let want = rect(-32000.0, -32000.0, 0.0, 0.0);
|
|
||||||
assert_eq!(fit_window_rect_to_screens(&[], want), want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn the_win32_minimized_sentinel_comes_back_onto_the_primary_display() {
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(-32000.0, -32000.0, 0.0, 0.0));
|
|
||||||
assert_eq!(fitted.pos, dvec2(0.0, 0.0));
|
|
||||||
assert_eq!(fitted.size, MIN_WINDOW_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_past_the_right_edge_is_pulled_back_in() {
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(1900.0, 50.0, 800.0, 600.0));
|
|
||||||
assert_eq!(fitted, rect(1120.0, 50.0, 800.0, 600.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_larger_than_the_work_area_is_capped_to_it() {
|
|
||||||
let screens = [ScreenGeom {
|
|
||||||
bounds: rect(0.0, 0.0, 1920.0, 1080.0),
|
|
||||||
work_area: rect(0.0, 0.0, 1920.0, 1040.0),
|
|
||||||
is_primary: true,
|
|
||||||
}];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(-500.0, -500.0, 4000.0, 4000.0));
|
|
||||||
assert_eq!(fitted, rect(0.0, 0.0, 1920.0, 1040.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_keeps_the_secondary_display_it_sits_on() {
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(1920.0, 0.0, 2560.0, 1440.0, false),
|
|
||||||
];
|
|
||||||
let want = rect(2000.0, 200.0, 800.0, 600.0);
|
|
||||||
assert_eq!(fit_window_rect_to_screens(&screens, want), want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_on_a_display_that_is_gone_moves_to_the_nearest_one() {
|
|
||||||
// The secondary display it was saved on is no longer attached.
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(3000.0, 200.0, 800.0, 600.0));
|
|
||||||
assert_eq!(fitted, rect(1120.0, 200.0, 800.0, 600.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_spanning_two_adjacent_displays_is_left_alone() {
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(1920.0, 0.0, 1920.0, 1080.0, false),
|
|
||||||
];
|
|
||||||
// The window straddles the seam but every pixel of it is on a display.
|
|
||||||
let want = rect(1720.0, 100.0, 800.0, 600.0);
|
|
||||||
assert_eq!(fit_window_rect_to_screens(&screens, want), want);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_over_a_gap_between_displays_moves_to_the_one_holding_most_of_it() {
|
|
||||||
// Displays side by side with a gap between them, as a mismatched pair produces.
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(2400.0, 0.0, 1920.0, 1080.0, false),
|
|
||||||
];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(1800.0, 100.0, 800.0, 600.0));
|
|
||||||
assert_eq!(fitted, rect(2400.0, 100.0, 800.0, 600.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_hanging_off_the_end_of_the_arrangement_is_pulled_in() {
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(1920.0, 0.0, 1920.0, 1080.0, false),
|
|
||||||
];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(3600.0, 100.0, 800.0, 600.0));
|
|
||||||
assert_eq!(fitted, rect(3040.0, 100.0, 800.0, 600.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_window_spanning_displays_of_different_heights_is_not_left_hanging() {
|
|
||||||
// The taller display sits lower, so the strip below the shorter one is off-desktop.
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(1920.0, 0.0, 1920.0, 1440.0, false),
|
|
||||||
];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(1600.0, 900.0, 800.0, 400.0));
|
|
||||||
assert!(fitted != rect(1600.0, 900.0, 800.0, 400.0));
|
|
||||||
assert!(screens.iter().any(|s| fitted.is_inside_of(s.work_area)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn non_finite_geometry_falls_back_to_the_primary_display() {
|
|
||||||
let screens = [
|
|
||||||
screen(-1920.0, 0.0, 1920.0, 1080.0, false),
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
];
|
|
||||||
let fitted =
|
|
||||||
fit_window_rect_to_screens(&screens, rect(f64::NAN, f64::INFINITY, f64::NAN, 600.0));
|
|
||||||
assert_eq!(fitted, rect(0.0, 0.0, 960.0, 540.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cocoa_bottom_left_coordinates_fit_the_same_way() {
|
|
||||||
// macOS reports the primary display at the origin with y growing upwards; a window
|
|
||||||
// saved below the display comes back inside it.
|
|
||||||
let screens = [ScreenGeom {
|
|
||||||
bounds: rect(0.0, 0.0, 1728.0, 1117.0),
|
|
||||||
work_area: rect(0.0, 76.0, 1728.0, 1004.0),
|
|
||||||
is_primary: true,
|
|
||||||
}];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(20.0, -400.0, 900.0, 700.0));
|
|
||||||
assert_eq!(fitted, rect(20.0, 76.0, 900.0, 700.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_display_smaller_than_the_minimum_size_still_fits_a_window() {
|
|
||||||
let screens = [screen(0.0, 0.0, 100.0, 60.0, true)];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(500.0, 500.0, 800.0, 600.0));
|
|
||||||
assert_eq!(fitted, rect(0.0, 0.0, 100.0, 60.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_negative_size_is_raised_to_the_minimum() {
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, rect(10.0, 10.0, -800.0, -600.0));
|
|
||||||
assert_eq!(fitted, rect(10.0, 10.0, MIN_WINDOW_SIZE.x, MIN_WINDOW_SIZE.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn coordinates_far_outside_the_integer_range_land_on_a_display() {
|
|
||||||
let screens = [screen(0.0, 0.0, 1920.0, 1080.0, true)];
|
|
||||||
for want in [
|
|
||||||
rect(1e300, 1e300, 800.0, 600.0),
|
|
||||||
rect(-1e300, -1e300, 800.0, 600.0),
|
|
||||||
rect(f64::MAX, f64::MIN, f64::MAX, f64::MAX),
|
|
||||||
] {
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, want);
|
|
||||||
assert!(fitted.is_inside_of(screens[0].work_area), "{fitted:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn every_fitted_rectangle_lies_within_some_work_area() {
|
|
||||||
let screens = [
|
|
||||||
screen(0.0, 0.0, 1920.0, 1080.0, true),
|
|
||||||
screen(1920.0, -200.0, 2560.0, 1440.0, false),
|
|
||||||
];
|
|
||||||
for want in [
|
|
||||||
rect(-32000.0, -32000.0, 0.0, 0.0),
|
|
||||||
rect(f64::NAN, f64::NAN, f64::NAN, f64::NAN),
|
|
||||||
rect(f64::INFINITY, f64::NEG_INFINITY, 1e12, -1e12),
|
|
||||||
rect(1e9, 1e9, 1e9, 1e9),
|
|
||||||
rect(4400.0, 1100.0, 300.0, 200.0),
|
|
||||||
rect(0.0, 0.0, 0.0, 0.0),
|
|
||||||
] {
|
|
||||||
let fitted = fit_window_rect_to_screens(&screens, want);
|
|
||||||
let areas: Vec<Rect> = screens.iter().map(|s| s.work_area).collect();
|
|
||||||
assert!(is_covered_by(&areas, fitted), "{want:?} fitted to {fitted:?}");
|
|
||||||
assert!(fitted.pos.x.is_finite() && fitted.pos.y.is_finite());
|
|
||||||
assert!(fitted.size.x > 0.0 && fitted.size.y > 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sanitizing_rejects_every_size_a_windowing_system_cannot_use() {
|
|
||||||
for bad in [
|
|
||||||
dvec2(0.0, 0.0),
|
|
||||||
dvec2(-800.0, -600.0),
|
|
||||||
dvec2(f64::NAN, f64::NAN),
|
|
||||||
dvec2(f64::INFINITY, 600.0),
|
|
||||||
dvec2(1.0, 1.0),
|
|
||||||
] {
|
|
||||||
let (_, size) = sanitize_window_geom(None, bad);
|
|
||||||
assert!(size.x >= MIN_WINDOW_SIZE.x && size.y >= MIN_WINDOW_SIZE.y, "{bad:?}");
|
|
||||||
assert!(size.x.is_finite() && size.y.is_finite(), "{bad:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_size_carrying_no_information_becomes_the_default_not_the_floor() {
|
|
||||||
// Restoring a 200x120 sliver from a zeroed state file is visible but useless.
|
|
||||||
for empty in [
|
|
||||||
dvec2(0.0, 0.0),
|
|
||||||
dvec2(-800.0, -600.0),
|
|
||||||
dvec2(0.0, 800.0),
|
|
||||||
dvec2(f64::NAN, f64::NAN),
|
|
||||||
] {
|
|
||||||
assert_eq!(sanitize_window_geom(None, empty).1, DEFAULT_WINDOW_SIZE, "{empty:?}");
|
|
||||||
}
|
|
||||||
// A small but real request is only raised to something grabbable.
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_window_geom(None, dvec2(50.0, 40.0)).1,
|
|
||||||
MIN_WINDOW_SIZE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sanitizing_keeps_a_usable_request_intact() {
|
|
||||||
let (pos, size) = sanitize_window_geom(Some(dvec2(-1200.0, 40.0)), dvec2(1280.0, 800.0));
|
|
||||||
// A position on a left-hand secondary display is legitimate and is not a size problem,
|
|
||||||
// so it survives untouched; fitting to the displays is a separate, later step.
|
|
||||||
assert_eq!(pos, Some(dvec2(-1200.0, 40.0)));
|
|
||||||
assert_eq!(size, dvec2(1280.0, 800.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sanitizing_drops_a_position_that_is_not_a_real_number() {
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_window_geom(Some(dvec2(f64::NAN, 0.0)), dvec2(800.0, 600.0)).0,
|
|
||||||
None
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
sanitize_window_geom(Some(dvec2(0.0, f64::INFINITY)), dvec2(800.0, 600.0)).0,
|
|
||||||
None
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_clamped_point_leaves_a_minimum_window_visible() {
|
|
||||||
let screens = [ScreenGeom {
|
|
||||||
bounds: rect(0.0, 0.0, 1920.0, 1080.0),
|
|
||||||
work_area: rect(0.0, 0.0, 1920.0, 1040.0),
|
|
||||||
is_primary: true,
|
|
||||||
}];
|
|
||||||
assert_eq!(
|
|
||||||
clamp_point_to_screens(&screens, dvec2(-32000.0, -32000.0)),
|
|
||||||
dvec2(0.0, 0.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
clamp_point_to_screens(&screens, dvec2(1e9, 1e9)),
|
|
||||||
dvec2(1920.0 - MIN_WINDOW_SIZE.x, 1040.0 - MIN_WINDOW_SIZE.y)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
clamp_point_to_screens(&screens, dvec2(f64::NAN, 5.0)),
|
|
||||||
dvec2(0.0, 0.0)
|
|
||||||
);
|
|
||||||
assert_eq!(clamp_point_to_screens(&screens, dvec2(40.0, 50.0)), dvec2(40.0, 50.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -247,10 +247,6 @@ fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option<Rc<Vec<u8>>> {
|
||||||
|
|
||||||
/// Try to load a resource from the packaged location on desktop.
|
/// Try to load a resource from the packaged location on desktop.
|
||||||
/// Returns None when not in packaged mode (package_root is None).
|
/// Returns None when not in packaged mode (package_root is None).
|
||||||
///
|
|
||||||
/// A relative `package_root` (the desktop packagers use `.` beside the executable) is searched
|
|
||||||
/// both from the working directory and from the executable's own directory, because a launcher
|
|
||||||
/// is free to start the process anywhere — see `crate::os::cx_native::exe_relative_path`.
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(target_arch = "wasm32"),
|
not(target_arch = "wasm32"),
|
||||||
not(any(target_os = "android", target_os = "ios", target_os = "tvos")),
|
not(any(target_os = "android", target_os = "ios", target_os = "tvos")),
|
||||||
|
|
@ -259,7 +255,10 @@ fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option<Rc<Vec<u8>>> {
|
||||||
fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option<Rc<Vec<u8>>> {
|
fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option<Rc<Vec<u8>>> {
|
||||||
let root = cx.package_root.as_deref()?;
|
let root = cx.package_root.as_deref()?;
|
||||||
let full_path = format!("{}/{}", root, dep_path);
|
let full_path = format!("{}/{}", root, dep_path);
|
||||||
crate::os::cx_native::read_file_cwd_or_exe_relative(&full_path).map(Rc::new)
|
let mut file = File::open(&full_path).ok()?;
|
||||||
|
let mut data = Vec::new();
|
||||||
|
file.read_to_end(&mut data).ok()?;
|
||||||
|
Some(Rc::new(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a file directly from the filesystem (desktop/mobile only, not wasm).
|
/// Load a file directly from the filesystem (desktop/mobile only, not wasm).
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use crate::{
|
||||||
makepad_math::*,
|
makepad_math::*,
|
||||||
//makepad_live_id::*,
|
//makepad_live_id::*,
|
||||||
makepad_script::*,
|
makepad_script::*,
|
||||||
screen::{sanitize_window_geom, DEFAULT_WINDOW_SIZE},
|
|
||||||
script::vm::*,
|
script::vm::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -514,8 +513,6 @@ impl WindowHandle {
|
||||||
cx.windows[self.window_id()].get_inner_size()
|
cx.windows[self.window_id()].get_inner_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The window's top-left corner, in the space [`Self::reposition`] accepts: physical
|
|
||||||
/// screen pixels on Windows and X11, points on macOS. Never scaled by the DPI factor.
|
|
||||||
pub fn get_position(&self, cx: &Cx) -> Vec2d {
|
pub fn get_position(&self, cx: &Cx) -> Vec2d {
|
||||||
cx.windows[self.window_id()].get_position()
|
cx.windows[self.window_id()].get_position()
|
||||||
}
|
}
|
||||||
|
|
@ -611,12 +608,6 @@ impl WindowHandle {
|
||||||
cx.push_unique_platform_op(CxOsOp::ResizeWindow(self.window_id(), size));
|
cx.push_unique_platform_op(CxOsOp::ResizeWindow(self.window_id(), size));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the window's top-left corner to `position`, in the same space
|
|
||||||
/// [`Self::get_position`] reports: physical screen pixels on Windows and X11, points on
|
|
||||||
/// macOS. Unlike [`Self::resize`], which takes a logical size that scales with the DPI, a
|
|
||||||
/// position is never scaled — a screen coordinate spanning displays of different scales
|
|
||||||
/// has no single factor to be logical in. Backends fit the request to the displays that
|
|
||||||
/// are actually attached, so a window cannot be placed where it could not be reached.
|
|
||||||
pub fn reposition(&self, cx: &mut Cx, position: Vec2d) {
|
pub fn reposition(&self, cx: &mut Cx, position: Vec2d) {
|
||||||
cx.push_unique_platform_op(CxOsOp::RepositionWindow(self.window_id(), position));
|
cx.push_unique_platform_op(CxOsOp::RepositionWindow(self.window_id(), position));
|
||||||
}
|
}
|
||||||
|
|
@ -703,22 +694,6 @@ impl Default for CxWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CxWindow {
|
impl CxWindow {
|
||||||
/// The geometry to create this window with, reduced to values a windowing system can act
|
|
||||||
/// on: a size no smaller than [`crate::screen::MIN_WINDOW_SIZE`], and a position that is
|
|
||||||
/// either real coordinates or `None` for "the system places it".
|
|
||||||
///
|
|
||||||
/// Every backend reads its creation geometry through here, so no request — a restored
|
|
||||||
/// state file, a DSL literal, a computed popup rect — can reach a platform call carrying a
|
|
||||||
/// size it will reject or a coordinate that is not a number. Placing the window on a
|
|
||||||
/// display that exists is a separate, per-backend step; see
|
|
||||||
/// [`crate::screen::fit_window_rect_to_screens`].
|
|
||||||
pub fn create_geom(&self) -> (Option<Vec2d>, Vec2d) {
|
|
||||||
sanitize_window_geom(
|
|
||||||
self.create_position,
|
|
||||||
self.create_inner_size.unwrap_or(DEFAULT_WINDOW_SIZE),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn valid_dpi_factor(dpi_factor: f64) -> Option<f64> {
|
pub(crate) fn valid_dpi_factor(dpi_factor: f64) -> Option<f64> {
|
||||||
if dpi_factor.is_finite() && dpi_factor > 0.0 {
|
if dpi_factor.is_finite() && dpi_factor > 0.0 {
|
||||||
Some(dpi_factor)
|
Some(dpi_factor)
|
||||||
|
|
|
||||||
|
|
@ -454,51 +454,6 @@ fn extract_workspace_patch_sections(workspace_manifest: &str) -> String {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_workspace_dependencies_section(workspace_manifest: &str) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
let mut current_section: Option<String> = None;
|
|
||||||
let mut current_body = Vec::new();
|
|
||||||
|
|
||||||
let flush_section =
|
|
||||||
|out: &mut String, current_section: &mut Option<String>, current_body: &mut Vec<String>| {
|
|
||||||
let Some(section) = current_section.take() else {
|
|
||||||
current_body.clear();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if section != "[workspace.dependencies]" {
|
|
||||||
current_body.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !out.is_empty() {
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
out.push_str(§ion);
|
|
||||||
out.push('\n');
|
|
||||||
for line in current_body.iter() {
|
|
||||||
out.push_str(line);
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
current_body.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
for raw_line in workspace_manifest.lines() {
|
|
||||||
let trimmed = raw_line.trim();
|
|
||||||
if trimmed.starts_with('[') && trimmed.ends_with(']') && !raw_line.starts_with(' ') {
|
|
||||||
flush_section(&mut out, &mut current_section, &mut current_body);
|
|
||||||
current_section = Some(trimmed.to_string());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if current_section.is_some() {
|
|
||||||
current_body.push(raw_line.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flush_section(&mut out, &mut current_section, &mut current_body);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_generated_wrapper_args(args: &[String], build_crate: &str) -> Vec<String> {
|
fn strip_generated_wrapper_args(args: &[String], build_crate: &str) -> Vec<String> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut skip_next = false;
|
let mut skip_next = false;
|
||||||
|
|
@ -601,15 +556,6 @@ fn generate_android_wrapper_manifest(
|
||||||
&workspace_root,
|
&workspace_root,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace_deps = extract_workspace_dependencies_section(&workspace_manifest);
|
|
||||||
if !workspace_deps.trim().is_empty() {
|
|
||||||
wrapper_manifest.push('\n');
|
|
||||||
wrapper_manifest.push_str(&rewrite_wrapper_manifest_paths(
|
|
||||||
&workspace_deps,
|
|
||||||
&workspace_root,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let wrapper_manifest_path = wrapper_dir.join("Cargo.toml");
|
let wrapper_manifest_path = wrapper_dir.join("Cargo.toml");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue