diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 096c5e9d..9415d06d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,29 +1,37 @@ name: Rust on: [push, pull_request] +env: + CARGO_TERM_COLOR: always jobs: build: - runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - + runs-on: ${{ matrix.os }} + env: + RUSTFLAGS: -D warnings + RUSTDOCFLAGS: -D warnings steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install XCB and GL dependencies - run: | - sudo apt update - sudo apt install libx11-xcb-dev libxcb-dri2-0-dev libgl1-mesa-dev libxcb-icccm4-dev libxcursor-dev if: contains(matrix.os, 'ubuntu') + run: sudo apt-get install libx11-dev libxcb1-dev libx11-xcb-dev libgl1-mesa-dev - name: Install rust stable - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: toolchain: stable - override: true - - name: Build with default features - run: cargo build --examples --workspace --verbose - - name: Build again with all features - run: cargo build --examples --workspace --all-features --verbose + components: rustfmt, clippy + - name: Build Default + run: cargo build --workspace --all-targets --verbose + - name: Build All Features + run: cargo build --workspace --all-targets --all-features --verbose - name: Run tests - run: cargo test --examples --workspace --all-features --verbose + run: cargo test --workspace --all-targets --all-features --verbose + - name: Check docs + run: cargo doc --examples --all-features --no-deps + - name: Clippy + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: Check Formatting (rustfmt) + run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index c1a0d9d9..2d65e460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ raw-window-handle = "0.5" [target.'cfg(target_os="linux")'.dependencies] x11rb = { version = "0.13.0", features = ["cursor", "resource_manager", "allow-unsafe-code"] } -x11 = { version = "2.21", features = ["xlib", "xcursor", "xlib_xcb"] } +x11 = { version = "2.21", features = ["xlib", "xlib_xcb"] } nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] @@ -43,3 +43,16 @@ softbuffer = "0.3.4" [workspace] members = ["examples/render_femtovg"] + +[lints.clippy] +missing-safety-doc = "allow" + +[[example]] +name = "open_window" +test = true +doctest = true + +[[example]] +name = "open_parented" +test = true +doctest = true diff --git a/README.md b/README.md index 1015ac32..e46ded9d 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,10 @@ Below is a proposed list of milestones (roughly in-order) and their status. Subj ### Linux -Install dependencies, e.g., +Install dependencies, e.g.: ```sh -sudo apt-get install libx11-dev libxcursor-dev libxcb-dri2-0-dev libxcb-icccm4-dev libx11-xcb-dev +sudo apt-get install libx11-dev libxcb1-dev libx11-xcb-dev libgl1-mesa-dev ``` ## License diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..1cc564c0 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +msrv = '1.59' +check-private-items = true diff --git a/examples/open_window.rs b/examples/open_window.rs index 354ee251..cf2a90af 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -40,7 +40,7 @@ impl WindowHandler for OpenWindowExample { fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { match &event { #[cfg(target_os = "macos")] - Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard(&"This is a test!"), + Event::Mouse(MouseEvent::ButtonPressed { .. }) => copy_to_clipboard("This is a test!"), Event::Window(WindowEvent::Resized(info)) => { println!("Resized: {:?}", info); let new_size = info.physical_size(); @@ -78,7 +78,7 @@ fn main() { std::thread::spawn(move || loop { std::thread::sleep(Duration::from_secs(5)); - if let Err(_) = tx.push(Message::Hello) { + if tx.push(Message::Hello).is_err() { println!("Failed sending message"); } }); diff --git a/src/gl/macos.rs b/src/gl/macos.rs index c9d84963..05f2aa20 100644 --- a/src/gl/macos.rs +++ b/src/gl/macos.rs @@ -121,10 +121,8 @@ impl GlContext { let framework_name = CFString::from_str("com.apple.opengl").unwrap(); let framework = unsafe { CFBundleGetBundleWithIdentifier(framework_name.as_concrete_TypeRef()) }; - let addr = unsafe { - CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) - }; - addr as *const c_void + + unsafe { CFBundleGetFunctionPointerForName(framework, symbol_name.as_concrete_TypeRef()) } } pub fn swap_buffers(&self) { diff --git a/src/macos/window.rs b/src/macos/window.rs index 15b9469e..f9455181 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -43,7 +43,6 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.state.window_inner.open.get() } - } unsafe impl HasRawWindowHandle for WindowHandle { @@ -289,7 +288,9 @@ impl<'a> Window<'a> { unsafe { let view = self.inner.ns_view.as_mut().unwrap(); let window: id = msg_send![view, window]; - if window == nil { return false; }; + if window == nil { + return false; + }; let first_responder: id = msg_send![window, firstResponder]; let is_key_window: BOOL = msg_send![window, isKeyWindow]; let is_focused: BOOL = msg_send![view, isEqual: first_responder]; diff --git a/src/win/cursor.rs b/src/win/cursor.rs new file mode 100644 index 00000000..f9a04d3b --- /dev/null +++ b/src/win/cursor.rs @@ -0,0 +1,54 @@ +use crate::MouseCursor; +use winapi::{ + shared::ntdef::LPCWSTR, + um::winuser::{ + IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, + IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, + }, +}; + +pub fn cursor_to_lpcwstr(cursor: MouseCursor) -> LPCWSTR { + match cursor { + MouseCursor::Default => IDC_ARROW, + MouseCursor::Hand => IDC_HAND, + MouseCursor::HandGrabbing => IDC_SIZEALL, + MouseCursor::Help => IDC_HELP, + // an empty LPCWSTR results in the cursor being hidden + MouseCursor::Hidden => std::ptr::null(), + + MouseCursor::Text => IDC_IBEAM, + MouseCursor::VerticalText => IDC_IBEAM, + + MouseCursor::Working => IDC_WAIT, + MouseCursor::PtrWorking => IDC_APPSTARTING, + + MouseCursor::NotAllowed => IDC_NO, + MouseCursor::PtrNotAllowed => IDC_NO, + + MouseCursor::ZoomIn => IDC_ARROW, + MouseCursor::ZoomOut => IDC_ARROW, + + MouseCursor::Alias => IDC_ARROW, + MouseCursor::Copy => IDC_ARROW, + MouseCursor::Move => IDC_SIZEALL, + MouseCursor::AllScroll => IDC_SIZEALL, + MouseCursor::Cell => IDC_CROSS, + MouseCursor::Crosshair => IDC_CROSS, + + MouseCursor::EResize => IDC_SIZEWE, + MouseCursor::NResize => IDC_SIZENS, + MouseCursor::NeResize => IDC_SIZENESW, + MouseCursor::NwResize => IDC_SIZENWSE, + MouseCursor::SResize => IDC_SIZENS, + MouseCursor::SeResize => IDC_SIZENWSE, + MouseCursor::SwResize => IDC_SIZENESW, + MouseCursor::WResize => IDC_SIZEWE, + MouseCursor::EwResize => IDC_SIZEWE, + MouseCursor::NsResize => IDC_SIZENS, + MouseCursor::NwseResize => IDC_SIZENWSE, + MouseCursor::NeswResize => IDC_SIZENESW, + + MouseCursor::ColResize => IDC_SIZEWE, + MouseCursor::RowResize => IDC_SIZENS, + } +} diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs index fd1c55bb..0ec0f8cf 100644 --- a/src/win/drop_target.rs +++ b/src/win/drop_target.rs @@ -15,7 +15,7 @@ use winapi::um::oleidl::{ IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_LINK, DROPEFFECT_MOVE, DROPEFFECT_NONE, DROPEFFECT_SCROLL, }; -use winapi::um::shellapi::DragQueryFileW; +use winapi::um::shellapi::{DragQueryFileW, HDROP}; use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl}; use winapi::um::winuser::CF_HDROP; use winapi::Interface; @@ -135,7 +135,7 @@ impl DropTarget { return; } - let hdrop = transmute((*medium.u).hGlobal()); + let hdrop = *(*medium.u).hGlobal() as HDROP; let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0); if item_count == 0 { @@ -153,7 +153,7 @@ impl DropTarget { DragQueryFileW( hdrop, i, - transmute(buffer.spare_capacity_mut().as_mut_ptr()), + buffer.spare_capacity_mut().as_mut_ptr().cast(), buffer_size as u32, ); buffer.set_len(buffer_size); @@ -175,7 +175,7 @@ impl DropTarget { return S_OK; } - return E_NOINTERFACE; + E_NOINTERFACE } unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG { diff --git a/src/win/mod.rs b/src/win/mod.rs index f66c2bdb..00effa43 100644 --- a/src/win/mod.rs +++ b/src/win/mod.rs @@ -1,3 +1,4 @@ +mod cursor; mod drop_target; mod keyboard; mod window; diff --git a/src/win/window.rs b/src/win/window.rs index 8e1ad407..ac7824b9 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,21 +1,22 @@ use winapi::shared::guiddef::GUID; -use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; +use winapi::shared::minwindef::{ATOM, FALSE, LOWORD, LPARAM, LRESULT, UINT, WPARAM}; use winapi::shared::windef::{HWND, RECT}; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::ole2::{OleInitialize, RegisterDragDrop, RevokeDragDrop}; use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, - GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, - ReleaseCapture, SetCapture, SetProcessDpiAwarenessContext, SetTimer, SetWindowLongPtrW, - SetWindowPos, SetFocus, GetFocus, TrackMouseEvent, TranslateMessage, UnregisterClassW, CS_OWNDC, - GET_XBUTTON_WPARAM, GWLP_USERDATA, IDC_ARROW, MSG, SWP_NOMOVE, SWP_NOZORDER, TRACKMOUSEEVENT, - WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, - WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, - WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, - WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, - WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, - WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, XBUTTON1, XBUTTON2, + GetDpiForWindow, GetFocus, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, + RegisterClassW, ReleaseCapture, SetCapture, SetCursor, SetFocus, SetProcessDpiAwarenessContext, + SetTimer, SetWindowLongPtrW, SetWindowPos, TrackMouseEvent, TranslateMessage, UnregisterClassW, + CS_OWNDC, GET_XBUTTON_WPARAM, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOMOVE, + SWP_NOZORDER, TRACKMOUSEEVENT, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, + WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, + WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, + WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, + XBUTTON1, XBUTTON2, }; use std::cell::{Cell, Ref, RefCell, RefMut}; @@ -37,6 +38,7 @@ use crate::{ WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; +use super::cursor::cursor_to_lpcwstr; use super::drop_target::DropTarget; use super::keyboard::KeyboardState; @@ -174,7 +176,7 @@ unsafe fn wnd_proc_inner( if *mouse_was_outside_window { // this makes Windows track whether the mouse leaves the window. // When the mouse leaves it results in a `WM_MOUSELEAVE` event. - let mut track_mouse =TRACKMOUSEEVENT { + let mut track_mouse = TRACKMOUSEEVENT { cbSize: std::mem::size_of::() as u32, dwFlags: winapi::um::winuser::TME_LEAVE, hwndTrack: hwnd, @@ -186,7 +188,12 @@ unsafe fn wnd_proc_inner( *mouse_was_outside_window = false; let enter_event = Event::Mouse(MouseEvent::CursorEntered); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, enter_event); + window_state + .handler + .borrow_mut() + .as_mut() + .unwrap() + .on_event(&mut window, enter_event); } let x = (lparam & 0xFFFF) as i16 as i32; @@ -423,6 +430,24 @@ unsafe fn wnd_proc_inner( None } + // If WM_SETCURSOR returns `None`, WM_SETCURSOR continues to get handled by the outer window(s), + // If it returns `Some(1)`, the current window decides what the cursor is + WM_SETCURSOR => { + let low_word = LOWORD(lparam as u32) as isize; + let mouse_in_window = low_word == HTCLIENT; + if mouse_in_window { + // Here we need to set the cursor back to what the state says, since it can have changed when outside the window + let cursor = + LoadCursorW(null_mut(), cursor_to_lpcwstr(window_state.cursor_icon.get())); + unsafe { + SetCursor(cursor); + } + Some(1) + } else { + // Cursor is being changed by some other window, e.g. when having mouse on the borders to resize it + None + } + } // NOTE: `WM_NCDESTROY` is handled in the outer function because this deallocates the window // state BV_WINDOW_MUST_CLOSE => { @@ -475,6 +500,7 @@ pub(super) struct WindowState { keyboard_state: RefCell, mouse_button_counter: Cell, mouse_was_outside_window: RefCell, + cursor_icon: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` handler: RefCell>>, _drop_target: RefCell>>, @@ -680,6 +706,7 @@ impl Window<'_> { keyboard_state: RefCell::new(KeyboardState::new()), mouse_button_counter: Cell::new(0), mouse_was_outside_window: RefCell::new(true), + cursor_icon: Cell::new(MouseCursor::Default), // The Window refers to this `WindowState`, so this `handler` needs to be // initialized later handler: RefCell::new(None), @@ -785,8 +812,12 @@ impl Window<'_> { self.state.deferred_tasks.borrow_mut().push_back(task); } - pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) { - todo!() + pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { + self.state.cursor_icon.set(mouse_cursor); + unsafe { + let cursor = LoadCursorW(null_mut(), cursor_to_lpcwstr(mouse_cursor)); + SetCursor(cursor); + } } #[cfg(feature = "opengl")] diff --git a/src/window.rs b/src/window.rs index 0008b7ce..25b53d1e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -23,7 +23,7 @@ pub struct WindowHandle { impl WindowHandle { fn new(window_handle: platform::WindowHandle) -> Self { - Self { window_handle, phantom: PhantomData::default() } + Self { window_handle, phantom: PhantomData } } /// Close the window diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index 11a84542..a5ea06d9 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -92,10 +92,8 @@ impl XcbConnection { let _xres = width_px * 25.4 / width_mm; let yres = height_px * 25.4 / height_mm; - let yscale = yres / 96.0; - // TODO: choose between `xres` and `yres`? (probably both are the same?) - yscale + yres / 96.0 } #[inline]