diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 168631806..ba4497fe9 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -52,7 +52,6 @@ mod performance_stats; pub mod memory_watchdog; pub mod perf_monitor; pub mod permission; -mod screen; mod texture; mod uniform_buffer; mod window; @@ -216,7 +215,6 @@ pub use { unregister_media_playback_session, MediaPlaybackSessionId, }, script::vm::*, - screen::{fit_window_rect_to_screens, ScreenGeom, MIN_WINDOW_SIZE}, shared_bytes::{MappedBytes, SharedBytes, SharedBytesStats}, texture::{ image_cache_use_mipmaps, Texture, TextureAnimation, TextureFormat, TextureId, diff --git a/platform/src/os/apple/macos/macos.rs b/platform/src/os/apple/macos/macos.rs index 178f44293..7f99a64e9 100644 --- a/platform/src/os/apple/macos/macos.rs +++ b/platform/src/os/apple/macos/macos.rs @@ -1539,12 +1539,11 @@ impl Cx { match op { CxOsOp::CreateWindow(window_id) => { let window = &mut self.windows[window_id]; - let (create_position, create_inner_size) = window.create_geom(); let mut metal_window = MetalWindow::new( window_id, &metal_cx, - create_inner_size, - create_position, + window.create_inner_size.unwrap_or(dvec2(800., 600.)), + window.create_position, &window.create_title, window.is_fullscreen, window.macos, diff --git a/platform/src/os/apple/macos/macos_window.rs b/platform/src/os/apple/macos/macos_window.rs index 2577b2ad2..f117b6365 100644 --- a/platform/src/os/apple/macos/macos_window.rs +++ b/platform/src/os/apple/macos/macos_window.rs @@ -6,7 +6,7 @@ use { MouseUpEvent, ScrollEvent, ScrollPhase, TextInputEvent, WindowCloseRequestedEvent, WindowDragQueryEvent, WindowDragQueryResponse, WindowGeom, WindowGeomChangeEvent, }, - makepad_math::{dvec2, Rect, Vec2d}, + makepad_math::{Rect, Vec2d}, os::{ apple::apple_sys::*, apple::apple_util::str_to_nsstring, @@ -18,7 +18,6 @@ use { macos_event::MacosEvent, }, }, - screen::{clamp_point_to_screens, fit_window_rect_to_screens, ScreenGeom}, window::{ MacosWindowChrome, MacosWindowConfig, MacosWindowKind, MacosWindowLevel, WindowBackdrop, WindowId, WindowVisuals, @@ -256,13 +255,9 @@ impl MacosWindow { let () = msg_send![self.view, setAllowedTouchTypes: 2u64]; 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 { - x: pinned.x, - y: pinned.y, + x: position.x as f64, + y: position.y as f64, } } else { NSPoint { x: 0., y: 0. } @@ -355,11 +350,6 @@ impl MacosWindow { if position.is_none() { 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 () = msg_send![input_context, invalidateCharacterCoordinates]; @@ -773,34 +763,12 @@ impl MacosWindow { let mut window_frame: NSRect = unsafe { msg_send![self.window, frame] }; window_frame.origin.x = pos.x as f64; window_frame.origin.y = pos.y as f64; - // A caller placing the window cannot know the display arrangement it is placing - // 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)); + //not very nice: CGDisplay::main().pixels_high() as f64 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 { let window_frame: NSRect = unsafe { msg_send![self.window, frame] }; Vec2d { @@ -1215,52 +1183,3 @@ pub fn get_cocoa_window(this: &Object) -> &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 { - 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 - } -} diff --git a/platform/src/os/cx_native.rs b/platform/src/os/cx_native.rs index 1f3517521..da447d131 100644 --- a/platform/src/os/cx_native.rs +++ b/platform/src/os/cx_native.rs @@ -1,13 +1,6 @@ use { crate::cx::Cx, - std::{ - fs::File, - io::prelude::*, - path::{Path, PathBuf}, - rc::Rc, - sync::OnceLock, - time::SystemTime, - }, + std::{fs::File, io::prelude::*, rc::Rc, time::SystemTime}, }; #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -17,58 +10,21 @@ pub enum EventFlow { Exit, } -/// The directory holding the running executable, queried once. -fn exe_dir() -> Option<&'static Path> { - static EXE_DIR: OnceLock> = 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) -> Option { - 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) -> Option> { - fn read(path: &Path) -> Option> { - let mut buffer = Vec::::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 impl Cx { pub fn native_load_dependencies(&mut self) { for (path, dep) in &mut self.dependencies { - if let Some(buffer) = read_file_cwd_or_exe_relative(path) { - dep.data = Some(Ok(Rc::new(buffer))); + if let Ok(mut file_handle) = File::open(path) { + let mut buffer = Vec::::new(); + if file_handle.read_to_end(&mut buffer).is_ok() { + dep.data = Some(Ok(Rc::new(buffer))); + } else { + dep.data = Some(Err("read_to_end failed".to_string())); + } } else { 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())); } } } diff --git a/platform/src/os/headless/event_loop.rs b/platform/src/os/headless/event_loop.rs index 5a45ba7d1..3d788a37b 100644 --- a/platform/src/os/headless/event_loop.rs +++ b/platform/src/os/headless/event_loop.rs @@ -633,13 +633,10 @@ impl Cx { } let window = &mut self.windows[window_id]; - let (position, inner_size) = window.create_geom(); - let inner_size = if window.create_inner_size.is_some() { - inner_size - } else { - dvec2(1920.0, 1080.0) - }; - let position = position.unwrap_or_else(|| dvec2(0.0, 0.0)); + let inner_size = window + .create_inner_size + .unwrap_or_else(|| dvec2(1920.0, 1080.0)); + let position = window.create_position.unwrap_or_else(|| dvec2(0.0, 0.0)); let dpi_factor = configured_headless_dpi(); let state = &mut windows[window_id.id()]; diff --git a/platform/src/os/linux/wayland/linux_wayland.rs b/platform/src/os/linux/wayland/linux_wayland.rs index ace77a28a..fe8862b70 100644 --- a/platform/src/os/linux/wayland/linux_wayland.rs +++ b/platform/src/os/linux/wayland/linux_wayland.rs @@ -634,7 +634,6 @@ impl WaylandCx { let compositor = state.compositor.as_ref().unwrap(); let wm_base = state.wm_base.as_ref().unwrap(); 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() { "Makepad" } else { @@ -651,8 +650,8 @@ impl WaylandCx { state.shm.as_ref(), self.qhandle.as_ref().unwrap(), gl_cx, - create_inner_size, - create_position, + window.create_inner_size.unwrap_or(dvec2(800., 600.)), + window.create_position, &window.create_title, app_id, window.is_fullscreen, @@ -766,9 +765,6 @@ impl WaylandCx { } } 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::SetWindowVisuals(_window_id, visuals) => { if visuals.backdrop != crate::window::WindowBackdrop::None { diff --git a/platform/src/os/linux/wayland/opengl_wayland.rs b/platform/src/os/linux/wayland/opengl_wayland.rs index 5a19f4d8b..bc25aa593 100644 --- a/platform/src/os/linux/wayland/opengl_wayland.rs +++ b/platform/src/os/linux/wayland/opengl_wayland.rs @@ -87,19 +87,8 @@ impl WaylandWindow { } base_surface.commit(); - // `wl_egl_window_create` rejects a non-positive extent, and a float-to-int cast turns - // both a negative and a NaN into zero, so the requested size is floored before the - // 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 wl_egl_surface = + WlEglSurface::new(base_surface.id(), inner_size.x as i32, inner_size.y as i32).unwrap(); let egl_surface = unsafe { (opengl_cx.libegl.eglCreateWindowSurface.unwrap())( opengl_cx.egl_display, diff --git a/platform/src/os/linux/x11/linux_x11.rs b/platform/src/os/linux/x11/linux_x11.rs index b5e680694..a36930176 100644 --- a/platform/src/os/linux/x11/linux_x11.rs +++ b/platform/src/os/linux/x11/linux_x11.rs @@ -550,12 +550,11 @@ impl X11Cx { CxOsOp::CreateWindow(window_id) => { let gl_cx = cx.os.opengl_cx.as_ref().unwrap(); let window = &cx.windows[window_id]; - let (create_position, create_inner_size) = window.create_geom(); let opengl_window = OpenglWindow::new( window_id, gl_cx, - create_inner_size, - create_position, + window.create_inner_size.unwrap_or(dvec2(800., 600.)), + window.create_position, &window.create_title, &window.create_app_id, window.is_fullscreen, diff --git a/platform/src/os/linux/x11/mod.rs b/platform/src/os/linux/x11/mod.rs index 73e842377..c56a271e5 100644 --- a/platform/src/os/linux/x11/mod.rs +++ b/platform/src/os/linux/x11/mod.rs @@ -1,7 +1,6 @@ pub mod linux_x11; pub mod linux_x11_stdin; pub mod opengl_x11; -pub mod x11_screen; pub mod x11_sys; pub mod xlib_app; pub mod xlib_event; diff --git a/platform/src/os/linux/x11/x11_screen.rs b/platform/src/os/linux/x11/x11_screen.rs deleted file mode 100644 index 3b7a74555..000000000 --- a/platform/src/os/linux/x11/x11_screen.rs +++ /dev/null @@ -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 { - 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 { - 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::::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 { - 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, - }] -} diff --git a/platform/src/os/linux/x11/xlib_window.rs b/platform/src/os/linux/x11/xlib_window.rs index 8e1ea9373..ab1920579 100644 --- a/platform/src/os/linux/x11/xlib_window.rs +++ b/platform/src/os/linux/x11/xlib_window.rs @@ -1,12 +1,6 @@ use { self::super::{x11_sys, xlib_app::*, xlib_event::XlibEvent}, - crate::{ - 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, - }, + crate::{area::Area, cursor::MouseCursor, event::*, makepad_math::{Rect, Vec2d}, window::WindowId}, std::{ cell::Cell, ffi::{CStr, CString}, @@ -116,26 +110,22 @@ impl XlibWindow { | x11_sys::LeaveWindowMask) as c_long; 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 - // 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( display, root_window, - create_x, - create_y, - clamp_extent(size.x * dpi_factor), - clamp_extent(size.y * dpi_factor), + if position.is_some() { + position.unwrap().x + } else { + 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, visual_info.depth, 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 { unsafe { 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) { unsafe { let display = get_xlib_app_global().display; - // A caller placing the window cannot know the desktop it is placing into, so the - // 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); + let dpi_factor = self.get_dpi_factor(); x11_sys::XMoveWindow( display, self.window.unwrap(), - clamp_coord(fitted.pos.x), - clamp_coord(fitted.pos.y), + (pos.x * dpi_factor) as i32, + (pos.y * dpi_factor) as i32, ); 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, - size: Vec2d, - dpi_factor: f64, -) -> (Option, 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 -} diff --git a/platform/src/os/windows/mod.rs b/platform/src/os/windows/mod.rs index ed84c865c..9e696b68f 100644 --- a/platform/src/os/windows/mod.rs +++ b/platform/src/os/windows/mod.rs @@ -12,7 +12,6 @@ pub mod video_file_decoder; pub mod video_file_encoder; pub mod wasapi; pub mod win32_event; -pub mod win32_screen; pub mod win32_window; pub mod windows_media; pub mod windows_media_engine_notify; diff --git a/platform/src/os/windows/win32_screen.rs b/platform/src/os/windows/win32_screen.rs deleted file mode 100644 index c7f070654..000000000 --- a/platform/src/os/windows/win32_screen.rs +++ /dev/null @@ -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 { - 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) }; - let mut info = MonitorInfo { - cb_size: size_of::() 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 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, - } -} diff --git a/platform/src/os/windows/win32_window.rs b/platform/src/os/windows/win32_window.rs index 1097c5e06..dc4f334a4 100644 --- a/platform/src/os/windows/win32_window.rs +++ b/platform/src/os/windows/win32_window.rs @@ -10,9 +10,7 @@ use { droptarget::*, win32_app::{encode_wide, with_win32_app, Win32App}, win32_event::*, - win32_screen::{win32_screens, workspace_rect_to_screen}, }, - screen::{clamp_point_to_screens, fit_window_rect_to_screens}, window::{WindowBackdrop, WindowId, WindowVisuals}, windows::{ core::PCWSTR, @@ -90,7 +88,6 @@ use { WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCCALCSIZE, WM_NCHITTEST, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, - GetWindowPlacement, WINDOWPLACEMENT, 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_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)] struct AccentPolicy { accent_state: u32, @@ -213,11 +203,6 @@ pub struct Win32Window { /// Set by `close_window()`; suppresses the WM_ACTIVATE-derived /// `PopupDismissed(FocusLost)`, which would duplicate the closer's dismissal. pub is_closing: Cell, - /// 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, pub ignore_wmsize: usize, pub hwnd: HWND, pub track_mouse_event: bool, @@ -533,22 +518,10 @@ impl Win32Window { let style_ex = WS_EX_WINDOWEDGE | WS_EX_APPWINDOW | WS_EX_ACCEPTFILES; - let (x, y) = match position { - // A restored position can name a display that is gone, or hold values no display - // 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 { - (CW_USEDEFAULT, CW_USEDEFAULT) - } - } - None => (CW_USEDEFAULT, CW_USEDEFAULT), + let (x, y) = if let Some(position) = position { + (position.x as i32, position.y as i32) + } else { + (CW_USEDEFAULT, CW_USEDEFAULT) }; let hwnd = unsafe { @@ -594,7 +567,6 @@ impl Win32Window { nc_dq_gen: Cell::new(0), geom_event_gen: Cell::new(0), is_closing: Cell::new(false), - in_size_move: Cell::new(false), ignore_wmsize: 0, hwnd, track_mouse_event: false, @@ -649,7 +621,6 @@ impl Win32Window { nc_dq_gen: Cell::new(0), geom_event_gen: Cell::new(0), is_closing: Cell::new(false), - in_size_move: Cell::new(false), ignore_wmsize: 0, hwnd, track_mouse_event: false, @@ -678,50 +649,6 @@ impl Win32Window { self.set_inner_size(size); if self.is_fullscreen { 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 => { - window.in_size_move.set(true); with_win32_app(|app| app.start_resize()); 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 => { - window.in_size_move.set(false); with_win32_app(|app| app.stop_resize()); 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 // 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 // DPI so send_change_event() (and subsequent hit-tests) re-read the new value. 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(); } WM_DPICHANGED => { @@ -1165,13 +1071,6 @@ impl Win32Window { 0x0003 => { window.nc_dq_cache.set(None); 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 => { // close requested @@ -1471,51 +1370,31 @@ impl Win32Window { 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::() 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 { - let rect = self.outer_rect(); - Vec2d { - x: rect.left as f64, - y: rect.top as f64, + unsafe { + let mut rect = RECT { + left: 0, + top: 0, + bottom: 0, + right: 0, + }; + GetWindowRect(self.hwnd, &mut rect).unwrap(); + Vec2d { + x: rect.left as f64, + y: rect.top as f64, + } } } pub fn get_inner_size(&self) -> Vec2d { unsafe { - let mut rect = RECT::default(); - if self.is_iconic() { - // A restored window of this backend is fully client-sized (see the - // `WM_NCCALCSIZE` handler), so its outer rectangle is also its client size. - rect = self.outer_rect(); - } else { - GetClientRect(self.hwnd, &mut rect).unwrap(); - } + let mut rect = RECT { + left: 0, + top: 0, + bottom: 0, + right: 0, + }; + GetClientRect(self.hwnd, &mut rect).unwrap(); let dpi = self.get_dpi_factor(); Vec2d { x: (rect.right - rect.left) as f64 / dpi, @@ -1525,19 +1404,22 @@ impl Win32Window { } pub fn get_outer_size(&self) -> Vec2d { - let rect = self.outer_rect(); - let dpi = self.get_dpi_factor(); - Vec2d { - x: (rect.right - rect.left) as f64 / dpi, - y: (rect.bottom - rect.top) as f64 / dpi, + 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(); + Vec2d { + x: (rect.right - rect.left) 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) { unsafe { let mut window_rect = RECT { @@ -1547,23 +1429,13 @@ impl Win32Window { right: 0, }; GetWindowRect(self.hwnd, &mut window_rect).unwrap(); - // A caller placing the window — restoring a saved position, cascading a new - // 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); + let dpi = self.get_dpi_factor(); MoveWindow( self.hwnd, - fitted.pos.x as i32, - fitted.pos.y as i32, - fitted.size.x as i32, - fitted.size.y as i32, + (pos.x * dpi) as i32, + (pos.y * dpi) as i32, + window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, false, ) .unwrap(); @@ -1723,27 +1595,6 @@ impl Win32Window { 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) { // Record that a geometry event is published (see `geom_event_gen`). self.geom_event_gen.set(self.geom_event_gen.get().wrapping_add(1)); diff --git a/platform/src/os/windows/windows.rs b/platform/src/os/windows/windows.rs index d48f92f82..c48dfbb63 100644 --- a/platform/src/os/windows/windows.rs +++ b/platform/src/os/windows/windows.rs @@ -758,12 +758,11 @@ impl Cx { match op { CxOsOp::CreateWindow(window_id) => { let window = &mut self.windows[window_id]; - let (create_position, create_inner_size) = window.create_geom(); let d3d11_window = D3d11Window::new( window_id, &d3d11_cx, - create_inner_size, - create_position, + window.create_inner_size.unwrap_or(dvec2(800., 600.)), + window.create_position, &window.create_title, window.is_fullscreen, ); diff --git a/platform/src/screen.rs b/platform/src/screen.rs deleted file mode 100644 index 779ab80a3..000000000 --- a/platform/src/screen.rs +++ /dev/null @@ -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 { - 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 = 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, size: Vec2d) -> (Option, 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 = 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)); - } -} diff --git a/platform/src/script/res.rs b/platform/src/script/res.rs index c01abc5e4..b7f5b5e16 100644 --- a/platform/src/script/res.rs +++ b/platform/src/script/res.rs @@ -247,10 +247,6 @@ fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option>> { /// Try to load a resource from the packaged location on desktop. /// 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( not(target_arch = "wasm32"), 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>> { fn load_packaged_resource(cx: &Cx, dep_path: &str) -> Option>> { let root = cx.package_root.as_deref()?; 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). diff --git a/platform/src/window.rs b/platform/src/window.rs index 0ad46c9dc..8deac7523 100644 --- a/platform/src/window.rs +++ b/platform/src/window.rs @@ -8,7 +8,6 @@ use crate::{ makepad_math::*, //makepad_live_id::*, makepad_script::*, - screen::{sanitize_window_geom, DEFAULT_WINDOW_SIZE}, script::vm::*, }; @@ -514,8 +513,6 @@ impl WindowHandle { 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 { cx.windows[self.window_id()].get_position() } @@ -611,12 +608,6 @@ impl WindowHandle { 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) { cx.push_unique_platform_op(CxOsOp::RepositionWindow(self.window_id(), position)); } @@ -703,22 +694,6 @@ impl Default for 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) { - 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 { if dpi_factor.is_finite() && dpi_factor > 0.0 { Some(dpi_factor) diff --git a/tools/cargo_makepad/src/android/compile.rs b/tools/cargo_makepad/src/android/compile.rs index 6a0111b51..4688a5cf3 100644 --- a/tools/cargo_makepad/src/android/compile.rs +++ b/tools/cargo_makepad/src/android/compile.rs @@ -454,51 +454,6 @@ fn extract_workspace_patch_sections(workspace_manifest: &str) -> String { out } -fn extract_workspace_dependencies_section(workspace_manifest: &str) -> String { - let mut out = String::new(); - let mut current_section: Option = None; - let mut current_body = Vec::new(); - - let flush_section = - |out: &mut String, current_section: &mut Option, current_body: &mut Vec| { - 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 { let mut out = Vec::new(); let mut skip_next = false; @@ -601,15 +556,6 @@ fn generate_android_wrapper_manifest( &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");