diff --git a/examples/open_window.rs b/examples/open_window.rs index fe77455..bc68483 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -1,4 +1,4 @@ -use baseview::{Event, Window, WindowHandler}; +use baseview::{Event, Window, WindowHandler, WindowSize, WindowScalePolicy}; struct OpenWindowExample; @@ -21,8 +21,8 @@ impl WindowHandler for OpenWindowExample { fn main() { let window_open_options = baseview::WindowOpenOptions { title: "baseview".into(), - width: 512, - height: 512, + size: WindowSize::Logical(baseview::Size::new(512.0, 512.0)), + scale: WindowScalePolicy::TrySystemScaleFactor, parent: baseview::Parent::None, }; diff --git a/src/event.rs b/src/event.rs index ef4f4c7..6fae242 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,3 +1,5 @@ +use crate::{WindowInfo, Point}; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum KeyboardEvent { KeyPressed(u32), @@ -39,18 +41,16 @@ pub enum ScrollDelta { pub struct MouseClick { pub button: MouseButton, pub click_count: usize, - pub x: i32, - pub y: i32, + /// The logical coordinates of the mouse position + pub position: Point, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum MouseEvent { /// The mouse cursor was moved CursorMoved { - /// The X coordinate of the mouse position - x: i32, - /// The Y coordinate of the mouse position - y: i32, + /// The logical coordinates of the mouse position + position: Point, }, /// A mouse button was pressed. @@ -72,14 +72,7 @@ pub enum MouseEvent { CursorLeft, } -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct WindowInfo { - pub width: u32, - pub height: u32, - pub scale: f64, -} - -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug)] pub enum WindowEvent { Resized(WindowInfo), Focused, diff --git a/src/lib.rs b/src/lib.rs index 4247d26..642ac54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,10 +18,15 @@ pub use macos::*; mod event; mod keyboard; mod mouse_cursor; +mod window_info; +mod window_open_options; pub use event::*; pub use keyboard::*; pub use mouse_cursor::MouseCursor; +pub use window_info::*; +pub use window_open_options::*; +#[derive(Debug)] pub enum Parent { None, AsIfParented, @@ -30,15 +35,6 @@ pub enum Parent { unsafe impl Send for Parent {} -pub struct WindowOpenOptions { - pub title: String, - - pub width: usize, - pub height: usize, - - pub parent: Parent, -} - pub trait WindowHandler { type Message; diff --git a/src/macos/window.rs b/src/macos/window.rs index e16f5b3..c682f61 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -12,7 +12,7 @@ use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle} use crate::{ Event, KeyboardEvent, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, - WindowOpenOptions, + WindowOpenOptions, WindowScalePolicy, }; pub struct Window { @@ -41,9 +41,22 @@ impl Window { unsafe { let _pool = NSAutoreleasePool::new(nil); + let scaling = match options.scale { + // TODO: Find system scale factor + WindowScalePolicy::TrySystemScaleFactor => get_scaling().unwrap_or(1.0), + WindowScalePolicy::TrySystemScaleFactorTimes(user_scale) => get_scaling().unwrap_or(1.0) * user_scale, + WindowScalePolicy::UseScaleFactor(user_scale) => user_scale, + WindowScalePolicy::NoScaling => 1.0, + }; + + let window_info = options.window_info_from_scale(scaling); + let rect = NSRect::new( NSPoint::new(0.0, 0.0), - NSSize::new(options.width as f64, options.height as f64), + NSSize::new( + window_info.logical_size().width as f64, + window_info.logical_size().height as f64 + ), ); let ns_window = NSWindow::alloc(nil) @@ -83,3 +96,8 @@ unsafe impl HasRawWindowHandle for Window { }) } } + +fn get_scaling() -> Option { + // TODO: find system scaling + None +} diff --git a/src/win/window.rs b/src/win/window.rs index aa1b7fb..bab6d9e 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -8,7 +8,7 @@ use winapi::um::winuser::{ SetWindowLongPtrA, TranslateMessage, UnregisterClassA, CS_OWNDC, GWLP_USERDATA, MB_ICONERROR, MB_OK, MB_TOPMOST, MSG, WM_CLOSE, WM_CREATE, WM_MOUSEMOVE, WM_PAINT, WM_SHOWWINDOW, WM_TIMER, WNDCLASSA, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, - WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, + WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, WM_DPICHANGED, }; use std::cell::RefCell; @@ -24,7 +24,7 @@ use raw_window_handle::{ use crate::{ Event, KeyboardEvent, MouseButton, MouseEvent, Parent::WithParent, ScrollDelta, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, Size, Point, PhySize, PhyPoint, }; unsafe fn message_box(title: &str, msg: &str) { @@ -78,11 +78,17 @@ unsafe extern "system" fn wnd_proc( WM_MOUSEMOVE => { let x = (lparam & 0xFFFF) as i32; let y = ((lparam >> 16) & 0xFFFF) as i32; - window_state.borrow_mut().handler.on_event( + + let physical_pos = PhyPoint { x, y }; + + let mut window_state = window_state.borrow_mut(); + + let logical_pos = physical_pos.to_logical(&window_state.window_info); + + window_state.handler.on_event( &mut window, Event::Mouse(MouseEvent::CursorMoved { - x: x as i32, - y: y as i32, + position: logical_pos, }), ); return 0; @@ -101,6 +107,9 @@ unsafe extern "system" fn wnd_proc( .on_event(&mut window, Event::Window(WindowEvent::WillClose)); return DefWindowProcA(hwnd, msg, wparam, lparam); } + WM_DPICHANGED => { + // TODO: Notify app of DPI change + } _ => {} } } @@ -134,7 +143,7 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) { struct WindowState { window_class: ATOM, - scaling: Option, // DPI scale, 96.0 is "default". + window_info: WindowInfo, handler: H, } @@ -184,13 +193,23 @@ impl Window { | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPSIBLINGS; + + let scaling = match options.scale { + // TODO: Find system scale factor + WindowScalePolicy::TrySystemScaleFactor => get_scaling().unwrap_or(1.0), + WindowScalePolicy::TrySystemScaleFactorTimes(user_scale) => get_scaling().unwrap_or(1.0) * user_scale, + WindowScalePolicy::UseScaleFactor(user_scale) => user_scale, + WindowScalePolicy::NoScaling => 1.0, + }; + + let window_info = options.window_info_from_scale(scaling); let mut rect = RECT { left: 0, top: 0, // todo: check if usize fits into i32 - right: options.width as i32, - bottom: options.height as i32, + right: window_info.physical_size().width as i32, + bottom: window_info.physical_size().height as i32, }; // todo: add check flags https://github.com/wrl/rutabaga/blob/f30ff67e157375cafdbafe5fb549f1790443a3a8/src/platform/win/window.c#L351 @@ -228,15 +247,13 @@ impl Window { let handler = build(&mut window); - let window_state = Rc::new(RefCell::new(WindowState { + let window_state = Box::new(RefCell::new(WindowState { window_class, - scaling: None, + window_info, handler, })); - let win = Rc::new(RefCell::new(window)); - - SetWindowLongPtrA(hwnd, GWLP_USERDATA, Rc::into_raw(win) as *const _ as _); + SetWindowLongPtrA(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _); SetTimer(hwnd, 4242, 13, None); WindowHandle { hwnd } @@ -252,3 +269,8 @@ unsafe impl HasRawWindowHandle for Window { }) } } + +fn get_scaling() -> Option { + // TODO: find system scaling + None +} \ No newline at end of file diff --git a/src/window_info.rs b/src/window_info.rs new file mode 100644 index 0000000..1dad5d5 --- /dev/null +++ b/src/window_info.rs @@ -0,0 +1,154 @@ +/// The info about the window +#[derive(Debug, Copy, Clone)] +pub struct WindowInfo { + logical_size: Size, + physical_size: PhySize, + scale: f64, + scale_recip: f64, +} + +impl WindowInfo { + pub fn from_logical_size(logical_size: Size, scale: f64) -> Self { + let scale_recip = if scale == 1.0 { 1.0 } else { 1.0 / scale }; + + let physical_size = PhySize { + width: (logical_size.width * scale).round() as u32, + height: (logical_size.height * scale).round() as u32, + }; + + Self { + logical_size, + physical_size, + scale, + scale_recip, + } + } + + pub fn from_physical_size(physical_size: PhySize, scale: f64) -> Self { + let scale_recip = if scale == 1.0 { 1.0 } else { 1.0 / scale }; + + let logical_size = Size { + width: f64::from(physical_size.width) * scale_recip, + height: f64::from(physical_size.height) * scale_recip, + }; + + Self { + logical_size, + physical_size, + scale, + scale_recip, + } + } + + /// The logical size of the window + pub fn logical_size(&self) -> Size { + self.logical_size + } + + /// The physical size of the window + pub fn physical_size(&self) -> PhySize { + self.physical_size + } + + /// The scale factor of the window + pub fn scale(&self) -> f64 { + self.scale + } + + /// The reciprocal of the scale factor of the window + pub fn scale_recip(&self) -> f64 { + self.scale_recip + } +} + +/// A point in logical coordinates +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Point { + pub x: f64, + pub y: f64 +} + +impl Point { + /// Create a new point in logical coordinates + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } + + /// Convert to actual physical coordinates + #[inline] + pub fn to_physical(&self, window_info: &WindowInfo) -> PhyPoint { + PhyPoint { + x: (self.x * window_info.scale()).round() as i32, + y: (self.y * window_info.scale()).round() as i32, + } + } +} + +/// A point in actual physical coordinates +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PhyPoint { + pub x: i32, + pub y: i32 +} + +impl PhyPoint { + /// Create a new point in actual physical coordinates + pub fn new(x: i32, y: i32) -> Self { + Self { x, y } + } + + /// Convert to logical coordinates + #[inline] + pub fn to_logical(&self, window_info: &WindowInfo) -> Point { + Point { + x: f64::from(self.x) * window_info.scale_recip(), + y: f64::from(self.y) * window_info.scale_recip(), + } + } +} + +/// A size in logical coordinates +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Size { + pub width: f64, + pub height: f64, +} + +impl Size { + /// Create a new size in logical coordinates + pub fn new(width: f64, height: f64) -> Self { + Self { width, height } + } + + /// Convert to actual physical size + #[inline] + pub fn to_physical(&self, window_info: &WindowInfo) -> PhySize { + PhySize { + width: (self.width * window_info.scale()).round() as u32, + height: (self.height * window_info.scale()).round() as u32, + } + } +} + +/// An actual size in physical coordinates +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct PhySize { + pub width: u32, + pub height: u32, +} + +impl PhySize { + /// Create a new size in actual physical coordinates + pub fn new(width: u32, height: u32) -> Self { + Self { width, height } + } + + /// Convert to logical size + #[inline] + pub fn to_logical(&self, window_info: &WindowInfo) -> Size { + Size { + width: f64::from(self.width) * window_info.scale_recip(), + height: f64::from(self.height) * window_info.scale_recip(), + } + } +} \ No newline at end of file diff --git a/src/window_open_options.rs b/src/window_open_options.rs new file mode 100644 index 0000000..a69b16e --- /dev/null +++ b/src/window_open_options.rs @@ -0,0 +1,46 @@ +use crate::{WindowInfo, Parent, Size, PhySize}; + +/// The size of the window +#[derive(Debug)] +pub enum WindowSize { + /// Use logical width and height + Logical(Size), + /// Use physical width and height + Physical(PhySize), +} + +/// The dpi scaling policy of the window +#[derive(Debug)] +pub enum WindowScalePolicy { + /// Try using the system scale factor + TrySystemScaleFactor, + /// Try using the system scale factor in addition to the given scale factor + TrySystemScaleFactorTimes(f64), + /// Use the given scale factor + UseScaleFactor(f64), + /// No scaling + NoScaling, +} + +/// The options for opening a new window +#[derive(Debug)] +pub struct WindowOpenOptions { + pub title: String, + + /// The size information about the window + pub size: WindowSize, + + /// The scaling of the window + pub scale: WindowScalePolicy, + + pub parent: Parent, +} + +impl WindowOpenOptions { + pub(crate) fn window_info_from_scale(&self, scale: f64) -> WindowInfo { + match self.size { + WindowSize::Logical(size) => WindowInfo::from_logical_size(size, scale), + WindowSize::Physical(size) => WindowInfo::from_physical_size(size, scale), + } + } +} \ No newline at end of file diff --git a/src/x11/window.rs b/src/x11/window.rs index 5e36af6..f20d637 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -12,7 +12,7 @@ use raw_window_handle::{ use super::XcbConnection; use crate::{ Event, KeyboardEvent, MouseButton, MouseCursor, MouseEvent, Parent, ScrollDelta, WindowEvent, - WindowHandler,WindowInfo, WindowOpenOptions, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, PhyPoint, PhySize, }; pub struct Window { @@ -24,7 +24,7 @@ pub struct Window { frame_interval: Duration, event_loop_running: bool, - new_size: Option<(u32, u32)> + new_physical_size: Option } // FIXME: move to outer crate context @@ -99,6 +99,17 @@ impl Window { ], ); + let scaling = match options.scale { + WindowScalePolicy::TrySystemScaleFactor => + xcb_connection.get_scaling().unwrap_or(1.0), + WindowScalePolicy::TrySystemScaleFactorTimes(user_scale) => + xcb_connection.get_scaling().unwrap_or(1.0) * user_scale, + WindowScalePolicy::UseScaleFactor(user_scale) => user_scale, + WindowScalePolicy::NoScaling => 1.0, + }; + + let window_info = options.window_info_from_scale(scaling); + let window_id = xcb_connection.conn.generate_id(); xcb::create_window( &xcb_connection.conn, @@ -107,8 +118,8 @@ impl Window { parent_id, 0, // x coordinate of the new window 0, // y coordinate of the new window - options.width as u16, // window width - options.height as u16, // window height + window_info.physical_size().width as u16, // window width + window_info.physical_size().height as u16, // window height 0, // window border xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, screen.root_visual(), @@ -151,14 +162,6 @@ impl Window { xcb_connection.conn.flush(); - let scaling = xcb_connection.get_scaling().unwrap_or(1.0); - - let window_info = WindowInfo { - width: options.width as u32, - height: options.height as u32, - scale: scaling, - }; - let mut window = Self { xcb_connection, window_id, @@ -168,7 +171,7 @@ impl Window { frame_interval: Duration::from_millis(15), event_loop_running: false, - new_size: None + new_physical_size: None, }; let mut handler = build(&mut window); @@ -208,15 +211,17 @@ impl Window { // the X server has a tendency to send spurious/extraneous configure notify events when a // window is resized, and we need to batch those together and just send one resize event // when they've all been coalesced. - self.new_size = None; + self.new_physical_size = None; while let Some(event) = self.xcb_connection.conn.poll_for_event() { self.handle_xcb_event(handler, event); } - if let Some((width, height)) = self.new_size.take() { - self.window_info.width = width; - self.window_info.height = height; + if let Some(size) = self.new_physical_size.take() { + self.window_info = WindowInfo::from_physical_size( + size, + self.window_info.scale() + ); handler.on_event(self, Event::Window( WindowEvent::Resized(self.window_info) @@ -319,11 +324,10 @@ impl Window { xcb::CONFIGURE_NOTIFY => { let event = unsafe { xcb::cast_event::(&event) }; - let new_size = (event.width() as u32, event.height() as u32); - let cur_size = (self.window_info.width, self.window_info.height); + let new_physical_size = PhySize::new(event.width() as u32, event.height() as u32); - if self.new_size.is_some() || new_size != cur_size { - self.new_size = Some(new_size); + if self.new_physical_size.is_some() || new_physical_size != self.window_info.physical_size() { + self.new_physical_size = Some(new_physical_size); } } @@ -335,11 +339,13 @@ impl Window { let detail = event.detail(); if detail != 4 && detail != 5 { + let physical_pos = PhyPoint::new(event.event_x() as i32, event.event_y() as i32); + let logical_pos = physical_pos.to_logical(&self.window_info); + handler.on_event( self, Event::Mouse(MouseEvent::CursorMoved { - x: event.event_x() as i32, - y: event.event_y() as i32, + position: logical_pos, }), ); } diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index 3819ee6..db2113a 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -13,6 +13,7 @@ use super::cursor; pub(crate) struct Atoms { pub wm_protocols: Option, pub wm_delete_window: Option, + pub wm_normal_hints: Option, } pub struct XcbConnection { @@ -45,7 +46,7 @@ impl XcbConnection { pub fn new() -> Result { let (conn, xlib_display) = xcb::Connection::connect_with_xlib_display()?; - let (wm_protocols, wm_delete_window) = intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW); + let (wm_protocols, wm_delete_window, wm_normal_hints) = intern_atoms!(&conn, WM_PROTOCOLS, WM_DELETE_WINDOW, WM_NORMAL_HINTS); Ok(Self { conn, @@ -54,6 +55,7 @@ impl XcbConnection { atoms: Atoms { wm_protocols, wm_delete_window, + wm_normal_hints, }, cursor_cache: HashMap::new()