From 756d72b18719a13d7ae002aaae2c23a970259bff Mon Sep 17 00:00:00 2001 From: mcrgnt Date: Sat, 8 Aug 2026 21:38:32 +0300 Subject: [PATCH] create window with x y position --- src/conf.rs | 17 +++++++++++++++++ src/native/linux_wayland.rs | 6 ++++++ src/native/linux_x11/libx11.rs | 2 ++ src/native/linux_x11/libx11_ex.rs | 13 +++++++++++-- src/native/linux_x11/x.rs | 4 +++- src/native/macos.rs | 13 ++++++++++++- src/native/windows.rs | 14 ++++++++++++-- 7 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/conf.rs b/src/conf.rs index 13cdc754..f62527fb 100644 --- a/src/conf.rs +++ b/src/conf.rs @@ -215,6 +215,19 @@ pub struct Conf { /// Defaults to `600`. pub window_height: i32, + /// Optional top-left window position in screen coordinates. + /// + /// `None` (default) lets the platform / window manager place the window. + /// Applied at window creation on Windows, Linux X11, and macOS. + /// Ignored on Wayland (compositor-owned placement), WASM, Android, and iOS. + /// + /// Using create-time position avoids a visible jump that happens when moving + /// the window with [`crate::window::set_window_position`] after it is mapped. + pub window_x: Option, + + /// See [`Conf::window_x`]. + pub window_y: Option, + /// If `true`, the rendering canvas is scaled for HighDPI displays. /// Defaults to `false`. pub high_dpi: bool, @@ -279,6 +292,8 @@ impl Default for Conf { window_title: "".to_owned(), window_width: 800, window_height: 600, + window_x: None, + window_y: None, high_dpi: false, fullscreen: false, sample_count: 1, @@ -296,6 +311,8 @@ impl Default for Conf { window_title: "".to_owned(), window_width: 800, window_height: 600, + window_x: None, + window_y: None, high_dpi: true, fullscreen: true, // sample_count: 1, diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs index f0a3179c..f7c86c7f 100644 --- a/src/native/linux_wayland.rs +++ b/src/native/linux_wayland.rs @@ -1180,6 +1180,12 @@ where display.decorations = decorations::Decorations::new(&mut display, conf); assert!(!display.xdg_toplevel.is_null()); + if conf.window_x.is_some() || conf.window_y.is_some() { + // Wayland compositors own toplevel placement; there is no portable + // absolute position API for regular windows. + eprintln!("miniquad: Conf.window_x/window_y ignored on Wayland"); + } + display.decorations.set_title( &mut display.client, display.xdg_toplevel, diff --git a/src/native/linux_x11/libx11.rs b/src/native/linux_x11/libx11.rs index 2cf8fba5..2ba52830 100644 --- a/src/native/linux_x11/libx11.rs +++ b/src/native/linux_x11/libx11.rs @@ -781,6 +781,8 @@ pub mod Xutil_h { pub colormap_size: libc::c_int, pub bits_per_rgb: libc::c_int, } + pub const USPosition: libc::c_long = (1 as libc::c_long) << 0 as libc::c_int; + pub const PPosition: libc::c_long = (1 as libc::c_long) << 2 as libc::c_int; pub const PMinSize: libc::c_long = (1 as libc::c_long) << 4 as libc::c_int; pub const PMaxSize: libc::c_long = (1 as libc::c_long) << 5 as libc::c_int; pub const PWinGravity: libc::c_long = (1 as libc::c_long) << 9 as libc::c_int; diff --git a/src/native/linux_x11/libx11_ex.rs b/src/native/linux_x11/libx11_ex.rs index 7a859d86..528cc2b6 100644 --- a/src/native/linux_x11/libx11_ex.rs +++ b/src/native/linux_x11/libx11_ex.rs @@ -202,11 +202,14 @@ impl LibX11 { | PropertyChangeMask; self.grab_error_handler(); + let pos_x = conf.window_x.map(|x| x as libc::c_int).unwrap_or(0); + let pos_y = conf.window_y.map(|y| y as libc::c_int).unwrap_or(0); + let window = (self.XCreateWindow)( display, root, - 0 as libc::c_int, - 0 as libc::c_int, + pos_x, + pos_y, conf.window_width as _, conf.window_height as _, 0 as libc::c_int as libc::c_uint, @@ -223,6 +226,12 @@ impl LibX11 { (self.XSetWMProtocols)(display, window, protocols.as_mut_ptr(), 1 as libc::c_int); let hints = (self.XAllocSizeHints)(); (*hints).flags |= PWinGravity; + if conf.window_x.is_some() || conf.window_y.is_some() { + // Ask the WM to honor create-time coordinates instead of its own placement. + (*hints).flags |= USPosition | PPosition; + (*hints).x = pos_x; + (*hints).y = pos_y; + } if !conf.window_resizable { (*hints).flags |= PMinSize | PMaxSize; (*hints).min_width = conf.window_width; diff --git a/src/native/linux_x11/x.rs b/src/native/linux_x11/x.rs index 816a49b1..1b7378f6 100644 --- a/src/native/linux_x11/x.rs +++ b/src/native/linux_x11/x.rs @@ -27,7 +27,7 @@ pub use Xresource_h::{ XrmDatabase, XrmDestroyDatabase, XrmGetResource, XrmGetStringDatabase, XrmValue, }; pub use Xutil_h::{ - IconicState, NormalState, PMinSize, PMaxSize, PWinGravity, WithdrawnState, XAllocSizeHints, XClassHint, + IconicState, NormalState, USPosition, PPosition, PMinSize, PMaxSize, PWinGravity, WithdrawnState, XAllocSizeHints, XClassHint, XComposeStatus, XLookupString, XSetWMNormalHints, XSizeHints, XVisualInfo, XWMHints, Xutf8SetWMProperties, }; @@ -896,6 +896,8 @@ pub mod Xutil_h { pub colormap_size: libc::c_int, pub bits_per_rgb: libc::c_int, } + pub const USPosition: libc::c_long = (1 as libc::c_long) << 0 as libc::c_int; + pub const PPosition: libc::c_long = (1 as libc::c_long) << 2 as libc::c_int; pub const PMinSize: libc::c_long = (1 as libc::c_long) << 4 as libc::c_int; pub const PMaxSize: libc::c_long = (1 as libc::c_long) << 5 as libc::c_int; pub const PWinGravity: libc::c_long = (1 as libc::c_long) << 9 as libc::c_int; diff --git a/src/native/macos.rs b/src/native/macos.rs index b8d01fde..aebc0d4b 100644 --- a/src/native/macos.rs +++ b/src/native/macos.rs @@ -1430,7 +1430,18 @@ where assert!(!view.is_null()); - let () = msg_send![window, center]; + if let (Some(x), Some(y)) = (conf.window_x, conf.window_y) { + // Conf uses top-left screen coords; Cocoa screen origin is bottom-left. + let main_screen: ObjcId = msg_send![class!(NSScreen), mainScreen]; + let screen_frame: NSRect = msg_send![main_screen, frame]; + let top_left = NSPoint { + x: x as f64, + y: screen_frame.size.height - (y as f64), + }; + let () = msg_send![window, setFrameTopLeftPoint: top_left]; + } else { + let () = msg_send![window, center]; + } let () = msg_send![window, setAcceptsMouseMovedEvents: YES]; if conf.fullscreen { diff --git a/src/native/windows.rs b/src/native/windows.rs index 3d2df69e..8437b45b 100644 --- a/src/native/windows.rs +++ b/src/native/windows.rs @@ -897,6 +897,8 @@ unsafe fn create_window( resizable: bool, width: i32, height: i32, + window_x: Option, + window_y: Option, ) -> (HWND, HDC) { let mut wndclassw: WNDCLASSW = std::mem::zeroed(); @@ -948,13 +950,19 @@ unsafe fn create_window( let class_name = "MINIQUADAPP\0".encode_utf16().collect::>(); let mut window_name = window_title.encode_utf16().collect::>(); window_name.push(0); + let (pos_x, pos_y) = match (window_x, window_y) { + (Some(x), Some(y)) => (x as i32, y as i32), + (Some(x), None) => (x as i32, CW_USEDEFAULT), + (None, Some(y)) => (CW_USEDEFAULT, y as i32), + (None, None) => (CW_USEDEFAULT, CW_USEDEFAULT), + }; let hwnd = CreateWindowExW( win_ex_style, // dwExStyle class_name.as_ptr(), // lpClassName window_name.as_ptr(), // lpWindowName win_style, // dwStyle - CW_USEDEFAULT, // X - CW_USEDEFAULT, // Y + pos_x, // X + pos_y, // Y win_width, // nWidth win_height, // nHeight NULL as _, // hWndParent @@ -1164,6 +1172,8 @@ where conf.window_resizable, conf.window_width as _, conf.window_height as _, + conf.window_x, + conf.window_y, ); if let Some(icon) = &conf.icon { set_icon(wnd, icon);