From 2e74c5819b390b981cb07c2d511bb3e6f738c489 Mon Sep 17 00:00:00 2001 From: Heng-Yi Wu <2316687+henry40408@users.noreply.github.com> Date: Sat, 6 Aug 2022 21:14:28 +0800 Subject: [PATCH] feat: always below bottom --- .changes/always-below-bottom.md | 5 +++ examples/window_debug.rs | 12 +++++++ src/platform_impl/android/mod.rs | 2 ++ src/platform_impl/ios/window.rs | 4 +++ src/platform_impl/linux/event_loop.rs | 3 ++ src/platform_impl/linux/window.rs | 17 +++++++++- src/platform_impl/macos/ffi.rs | 1 + src/platform_impl/macos/window.rs | 19 ++++++++++- src/platform_impl/windows/event_loop.rs | 7 ++++ src/platform_impl/windows/window.rs | 12 +++++++ src/platform_impl/windows/window_state.rs | 39 +++++++++++++++++------ src/window.rs | 27 ++++++++++++++++ 12 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 .changes/always-below-bottom.md diff --git a/.changes/always-below-bottom.md b/.changes/always-below-bottom.md new file mode 100644 index 000000000..1316a228a --- /dev/null +++ b/.changes/always-below-bottom.md @@ -0,0 +1,5 @@ +--- +"tao": "patch" +--- + +Implement "always below bottom" as contrary to "always on top". diff --git a/examples/window_debug.rs b/examples/window_debug.rs index cdec7b19e..b52a36b85 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -31,7 +31,11 @@ fn main() { eprintln!(" (Q) Quit event loop"); eprintln!(" (V) Toggle visibility"); eprintln!(" (X) Toggle maximized"); + eprintln!(" (T) Toggle always on top"); + eprintln!(" (B) Toggle always below bottom"); + let mut always_below_bottom = false; + let mut always_on_top = false; let mut visible = true; event_loop.run(move |event, _, control_flow| { @@ -120,6 +124,14 @@ fn main() { "x" => { window.set_maximized(!window.is_maximized()); } + "t" => { + always_on_top = !always_on_top; + window.set_always_on_top(always_on_top); + } + "b" => { + always_below_bottom = !always_below_bottom; + window.set_always_below_bottom(always_below_bottom); + } _ => (), }, Event::WindowEvent { diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 3694f33bd..6a8b5686c 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -642,6 +642,8 @@ impl Window { pub fn set_decorations(&self, _decorations: bool) {} + pub fn set_always_below_bottom(&self, _always_below_bottom: bool) {} + pub fn set_always_on_top(&self, _always_on_top: bool) {} pub fn set_window_icon(&self, _window_icon: Option) {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index be04e2ad6..99b55fe53 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -300,6 +300,10 @@ impl Inner { warn!("`Window::set_decorations` is ignored on iOS") } + pub fn set_always_below_bottom(&self, _always_below_bottom: bool) { + warn!("`Window::set_always_below_bottom` is ignored on iOS") + } + pub fn set_always_on_top(&self, _always_on_top: bool) { warn!("`Window::set_always_on_top` is ignored on iOS") } diff --git a/src/platform_impl/linux/event_loop.rs b/src/platform_impl/linux/event_loop.rs index 9dde9abe9..e495eb6cf 100644 --- a/src/platform_impl/linux/event_loop.rs +++ b/src/platform_impl/linux/event_loop.rs @@ -258,6 +258,9 @@ impl EventLoop { None => window.unfullscreen(), }, WindowRequest::Decorations(decorations) => window.set_decorated(decorations), + WindowRequest::AlwaysBelowBottom(always_below_bottom) => { + window.set_keep_below(always_below_bottom) + } WindowRequest::AlwaysOnTop(always_on_top) => window.set_keep_above(always_on_top), WindowRequest::WindowIcon(window_icon) => { if let Some(icon) = window_icon { diff --git a/src/platform_impl/linux/window.rs b/src/platform_impl/linux/window.rs index e3b2a4392..331967e2c 100644 --- a/src/platform_impl/linux/window.rs +++ b/src/platform_impl/linux/window.rs @@ -215,7 +215,12 @@ impl Window { window.set_visible(attributes.visible); window.set_decorated(attributes.decorations); - window.set_keep_above(attributes.always_on_top); + if attributes.always_below_bottom && !attributes.always_on_top { + window.set_keep_below(attributes.always_below_bottom); + } + if attributes.always_on_top && !attributes.always_below_bottom { + window.set_keep_above(attributes.always_on_top); + } if let Some(icon) = attributes.window_icon { window.set_icon(Some(&icon.inner.into())); } @@ -552,6 +557,15 @@ impl Window { } } + pub fn set_always_below_bottom(&self, always_below_bottom: bool) { + if let Err(e) = self.window_requests_tx.send(( + self.window_id, + WindowRequest::AlwaysBelowBottom(always_below_bottom), + )) { + log::warn!("Fail to send always on bottom request: {}", e); + } + } + pub fn set_always_on_top(&self, always_on_top: bool) { if let Err(e) = self .window_requests_tx @@ -757,6 +771,7 @@ pub enum WindowRequest { DragWindow, Fullscreen(Option), Decorations(bool), + AlwaysBelowBottom(bool), AlwaysOnTop(bool), WindowIcon(Option), UserAttention(Option), diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 839ee640e..7f8611704 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -116,6 +116,7 @@ pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; #[derive(Debug, Clone, Copy)] #[repr(isize)] pub enum NSWindowLevel { + BelowNormalWindowLevel = (kCGBaseWindowLevelKey - 1) as _, NSNormalWindowLevel = kCGBaseWindowLevelKey as _, NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index cc513f594..3e157de4c 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -232,13 +232,20 @@ fn create_window( ns_window.setMovableByWindowBackground_(YES); } - if attrs.always_on_top { + if attrs.always_on_top && !attrs.always_below_bottom { let _: () = msg_send![ *ns_window, setLevel: ffi::NSWindowLevel::NSFloatingWindowLevel ]; } + if attrs.always_below_bottom && !attrs.always_on_top { + let _: () = msg_send![ + *ns_window, + setLevel: ffi::NSWindowLevel::BelowNormalWindowLevel + ]; + } + if let Some(increments) = pl_attrs.resize_increments { let (x, y) = (increments.width, increments.height); if x >= 1.0 && y >= 1.0 { @@ -1125,6 +1132,16 @@ impl UnownedWindow { } } + #[inline] + pub fn set_always_below_bottom(&self, always_below_bottom: bool) { + let level = if always_below_bottom { + ffi::NSWindowLevel::BelowNormalWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(*self.ns_window, level) }; + } + #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { let level = if always_on_top { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index ea553b4e4..06aad21a8 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1076,6 +1076,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_WINDOWPOSCHANGING => { let mut window_state = subclass_input.window_state.lock(); + if let Some(ref mut fullscreen) = window_state.fullscreen { let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); let new_rect = RECT { @@ -1151,6 +1152,12 @@ unsafe fn public_window_callback_inner( } } + let window_flags = window_state.window_flags; + if window_flags.contains(WindowFlags::ALWAYS_BELOW_BOTTOM) { + let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); + window_pos.hwndInsertAfter = HWND_BOTTOM; + } + result = ProcResult::Value(LRESULT(0)); } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index bd0f3838b..833776301 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -655,6 +655,18 @@ impl Window { }); } + #[inline] + pub fn set_always_below_bottom(&self, always_below_bottom: bool) { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::ALWAYS_BELOW_BOTTOM, always_below_bottom) + }); + }); + } + #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { let window = self.window.clone(); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index dc6fc7d0a..5d3a21289 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -69,16 +69,17 @@ bitflags! { } bitflags! { pub struct WindowFlags: u32 { - const RESIZABLE = 1 << 0; - const DECORATIONS = 1 << 1; - const VISIBLE = 1 << 2; - const ON_TASKBAR = 1 << 3; - const ALWAYS_ON_TOP = 1 << 4; - const NO_BACK_BUFFER = 1 << 5; - const TRANSPARENT = 1 << 6; - const CHILD = 1 << 7; - const MAXIMIZED = 1 << 8; - const POPUP = 1 << 14; + const RESIZABLE = 1 << 0; + const DECORATIONS = 1 << 1; + const VISIBLE = 1 << 2; + const ON_TASKBAR = 1 << 3; + const ALWAYS_ON_TOP = 1 << 4; + const NO_BACK_BUFFER = 1 << 5; + const TRANSPARENT = 1 << 6; + const CHILD = 1 << 7; + const MAXIMIZED = 1 << 8; + const POPUP = 1 << 14; + const ALWAYS_BELOW_BOTTOM = 1 << 16; /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// included here to make masking easier. @@ -297,6 +298,24 @@ impl WindowFlags { } } + if diff.contains(WindowFlags::ALWAYS_BELOW_BOTTOM) { + unsafe { + SetWindowPos( + window, + match new.contains(WindowFlags::ALWAYS_BELOW_BOTTOM) { + true => HWND_BOTTOM, + false => HWND_NOTOPMOST, + }, + 0, + 0, + 0, + 0, + SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, + ); + InvalidateRgn(window, HRGN::default(), false); + } + } + if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { unsafe { ShowWindow( diff --git a/src/window.rs b/src/window.rs index 389d326c4..8596ec80e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -190,6 +190,11 @@ pub struct WindowAttributes { /// The default is `false`. pub always_on_top: bool, + /// Whether the window should always be on bottom of other windows. + /// + /// The default is `false`. + pub always_below_bottom: bool, + /// The window icon. /// /// The default is `None`. @@ -219,6 +224,7 @@ impl Default for WindowAttributes { transparent: false, decorations: true, always_on_top: false, + always_below_bottom: false, window_icon: None, window_menu: None, preferred_theme: None, @@ -361,6 +367,17 @@ impl WindowBuilder { self } + /// Sets whether or not the window will always be below other windows. + /// + /// See [`Window::set_always_below_bottom`] for details. + /// + /// [`Window::set_always_below_bottom`]: crate::window::Window::set_always_below_bottom + #[inline] + pub fn with_always_below_bottom(mut self, always_below_bottom: bool) -> Self { + self.window.always_below_bottom = always_below_bottom; + self + } + /// Sets whether or not the window will always be on top of other windows. /// /// See [`Window::set_always_on_top`] for details. @@ -784,6 +801,16 @@ impl Window { self.window.set_decorations(decorations) } + /// Change whether or not the window will always be below other windows. + /// + /// ## Platform-specific + /// + /// - **iOS / Android:** Unsupported. + #[inline] + pub fn set_always_below_bottom(&self, always_below_bottom: bool) { + self.window.set_always_below_bottom(always_below_bottom) + } + /// Change whether or not the window will always be on top of other windows. /// /// ## Platform-specific