From f07da6ad7bfce8ff7eb52facec1d788594afadda Mon Sep 17 00:00:00 2001 From: Jerome Humbert Date: Sun, 25 Apr 2021 18:00:15 +0100 Subject: [PATCH 1/2] Implement tooltip level on Windows platform Implement `WindowLevel::Tooltip` on Windows platform. Before change: - Tooltip window appear in taskbar - Window has strange min-size (probably due to OS min constraints) - Window steals focus from main app window After change: - Tooltip window doesn't appear in taskbar - Window fits any size (tested with nursery's `tooltip.rs`) - Window doesn't steal focus from main app window --- druid-shell/src/platform/windows/window.rs | 102 ++++++++++++++++----- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 1abb6f4b9b..81c33b1a11 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -98,6 +98,7 @@ pub(crate) struct WindowBuilder { transparent: bool, min_size: Option, position: Option, + level: Option, state: window::WindowState, } @@ -223,6 +224,9 @@ struct WindowState { is_resizable: Cell, handle_titlebar: Cell, active_text_input: Cell>, + // Is the window focusable ("activatable" in Win32 terminology)? + // False for tooltips, to prevent stealing focus from owner window. + is_focusable: bool, } /// Generic handler trait for the winapi window procedure entry point. @@ -385,7 +389,13 @@ fn set_style(hwnd: HWND, resizable: bool, titlebar: bool) { 0, 0, 0, - SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOSIZE, + SWP_SHOWWINDOW + | SWP_NOMOVE + | SWP_NOZORDER + | SWP_FRAMECHANGED + | SWP_NOSIZE + | SWP_NOOWNERZORDER + | SWP_NOACTIVATE, ) == 0 { warn!( @@ -543,7 +553,7 @@ impl MyWndProc { 0, size_px.width.round() as i32, size_px.height.round() as i32, - SWP_NOMOVE | SWP_NOZORDER, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, ) == 0 { warn!( @@ -561,7 +571,7 @@ impl MyWndProc { pos_px.y.round() as i32, 0, 0, - SWP_NOSIZE | SWP_NOZORDER, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, ) == 0 { warn!( @@ -579,12 +589,16 @@ impl MyWndProc { set_style(hwnd, resizable, self.has_titlebar()); } DeferredOp::SetWindowState(val) => unsafe { - let s = match val { - window::WindowState::Maximized => SW_MAXIMIZE, - window::WindowState::Minimized => SW_MINIMIZE, - window::WindowState::Restored => SW_RESTORE, - }; - ShowWindow(hwnd, s); + if self.handle.borrow().is_focusable() { + let s = match val { + window::WindowState::Maximized => SW_MAXIMIZE, + window::WindowState::Minimized => SW_MINIMIZE, + window::WindowState::Restored => SW_RESTORE, + }; + ShowWindow(hwnd, s); + } else { + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + } }, DeferredOp::SaveAs(options, token) => { let info = unsafe { @@ -747,7 +761,9 @@ impl WndProc for MyWndProc { | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED - | SWP_NOSIZE, + | SWP_NOSIZE + | SWP_NOOWNERZORDER + | SWP_NOACTIVATE, ) == 0 { warn!( @@ -813,7 +829,11 @@ impl WndProc for MyWndProc { (*rect).top, (*rect).right - (*rect).left, (*rect).bottom - (*rect).top, - SWP_NOZORDER | SWP_FRAMECHANGED | SWP_DRAWFRAME, + SWP_NOZORDER + | SWP_FRAMECHANGED + | SWP_DRAWFRAME + | SWP_NOOWNERZORDER + | SWP_NOACTIVATE, ); Some(0) }, @@ -1233,6 +1253,7 @@ impl WindowBuilder { size: None, min_size: None, position: None, + level: None, state: window::WindowState::Restored, } } @@ -1287,8 +1308,13 @@ impl WindowBuilder { self.state = state; } - pub fn set_level(&mut self, _level: WindowLevel) { - warn!("WindowBuilder::set_level is currently unimplemented for Windows platforms."); + pub fn set_level(&mut self, level: WindowLevel) { + match level { + WindowLevel::AppWindow | WindowLevel::Tooltip => self.level = Some(level), + _ => { + warn!("WindowBuilder::set_level({:?}) is currently unimplemented for Windows platforms.", level); + } + } } pub fn build(self) -> Result { @@ -1330,6 +1356,28 @@ impl WindowBuilder { None => (0 as HMENU, None, false), }; + let mut dwStyle = WS_OVERLAPPEDWINDOW; + let mut dwExStyle: DWORD = 0; + let mut focusable = true; + if let Some(level) = self.level { + match level { + WindowLevel::AppWindow => (), + WindowLevel::Tooltip => { + dwStyle = WS_POPUP; + dwExStyle = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW; + focusable = false; + } + WindowLevel::DropDown => { + dwStyle = WS_CHILD; + dwExStyle = 0; + } + WindowLevel::Modal => { + dwStyle = WS_OVERLAPPED; + dwExStyle = WS_EX_TOPMOST; + } + } + } + let window = WindowState { hwnd: Cell::new(0 as HWND), scale: Cell::new(scale), @@ -1345,6 +1393,7 @@ impl WindowBuilder { is_transparent: Cell::new(self.transparent), handle_titlebar: Cell::new(false), active_text_input: Cell::new(None), + is_focusable: focusable, }; let win = Rc::new(window); let handle = WindowHandle { @@ -1367,14 +1416,13 @@ impl WindowBuilder { }; win.wndproc.connect(&handle, state); - let mut dwStyle = WS_OVERLAPPEDWINDOW; if !self.resizable { dwStyle &= !(WS_THICKFRAME | WS_MAXIMIZEBOX); } if !self.show_titlebar { dwStyle &= !(WS_MINIMIZEBOX | WS_SYSMENU | WS_OVERLAPPED); } - let mut dwExStyle = 0; + if self.present_strategy == PresentStrategy::Flip { dwExStyle |= WS_EX_NOREDIRECTIONBITMAP; } @@ -1413,7 +1461,7 @@ impl WindowBuilder { 0, size_px.width.round() as i32, size_px.height.round() as i32, - SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE, + SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER, ) == 0 { warn!( @@ -1682,12 +1730,16 @@ impl WindowHandle { if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); unsafe { - let show = match self.get_window_state() { - window::WindowState::Maximized => SW_MAXIMIZE, - window::WindowState::Minimized => SW_MINIMIZE, - _ => SW_SHOWNORMAL, - }; - ShowWindow(hwnd, show); + if w.is_focusable { + let show = match self.get_window_state() { + window::WindowState::Maximized => SW_MAXIMIZE, + window::WindowState::Minimized => SW_MINIMIZE, + _ => SW_SHOWNORMAL, + }; + ShowWindow(hwnd, show); + } else { + ShowWindow(hwnd, SW_SHOWNOACTIVATE); + } UpdateWindow(hwnd); } } @@ -2061,6 +2113,12 @@ impl WindowHandle { self.state.upgrade().map(|w| w.hwnd.get()) } + /// Check whether the window can receive keyboard focus. This is generally true, + /// except for special windows like tooltips. + pub fn is_focusable(&self) -> bool { + self.state.upgrade().map(|w| w.is_focusable).unwrap_or(true) + } + /// Get a handle that can be used to schedule an idle task. pub fn get_idle_handle(&self) -> Option { self.state.upgrade().map(|w| IdleHandle { From 760e901e35f4d9c1222ad7f03cb7ae092fc30eb5 Mon Sep 17 00:00:00 2001 From: Jerome Humbert Date: Sat, 1 May 2021 22:44:04 +0100 Subject: [PATCH 2/2] Added suggestion change and CHANGELOG entry --- CHANGELOG.md | 9 +++++- druid-shell/src/platform/windows/window.rs | 36 ++++++++++++---------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ea902a76..ce7b0b673d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,15 @@ The latest published Druid release is [0.7.0](#070---2021-01-01) which was released on 2021-01-01. You can find its changes [documented below](#070---2021-01-01). -# Unreleased +## Unreleased ### Highlights + - International text input support (IME) on macOS. - Rich text and complex script support on Linux. ### Added + - Add `scroll()` method in WidgetExt ([#1600] by [@totsteps]) - `write!` for `RichTextBuilder` ([#1596] by [@Maan2003]) - Sub windows: Allow opening windows that share state with arbitrary parts of the widget hierarchy ([#1254] by [@rjwittams]) @@ -34,6 +36,7 @@ You can find its changes [documented below](#070---2021-01-01). context-methods to implement disabled ([#1632] by [@xarvic]) - `LifeCycle::BuildFocusChain` to update the focus-chain ([#1632] by [@xarvic]) - `DisabledIf` widget wrapper to disable based on the state of Data and Env ([#1702] by [@xarvic]) + ### Changed - Warn on unhandled Commands ([#1533] by [@Maan2003]) @@ -53,6 +56,7 @@ You can find its changes [documented below](#070---2021-01-01). ### Removed ### Fixed + - `Notification`s will not be delivered to the widget that sends them ([#1640] by [@cmyr]) - `TextBox` can handle standard keyboard shortcuts without needing menus ([#1660] by [@cmyr]) - GTK Shell: Prevent mangling of newline characters in clipboard ([#1695] by [@ForLoveOfCats]) @@ -60,6 +64,7 @@ You can find its changes [documented below](#070---2021-01-01). - Correctly capture and use stroke properties when rendering SVG paths ([#1647] by [@SecondFlight]) - focus-chain now only includes non hidden (`should_propagate_to_hidden()` on `Event` and `Lifecylce`) widgets ([#1724] by [@xarvic]) - Fixed layout of scrollbar with very small viewports ([#1715] by [@andrewhickman]) +- Fixed `WindowLevel::Tooltip` on Windows platform ([#1737] by [@djeedai]) ### Visual @@ -457,6 +462,7 @@ Last release without a changelog :( [@xarvic]: https://github.com/xarvic [@arthmis]: https://github.com/arthmis [@ccqpein]: https://github.com/ccqpein +[@djeedai]: https://github.com/djeedai [#599]: https://github.com/linebender/druid/pull/599 [#611]: https://github.com/linebender/druid/pull/611 @@ -684,6 +690,7 @@ Last release without a changelog :( [#1713]: https://github.com/linebender/druid/pull/1713 [#1715]: https://github.com/linebender/druid/pull/1715 [#1724]: https://github.com/linebender/druid/pull/1724 +[#1737]: https://github.com/linebender/druid/pull/1737 [Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master [0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0 diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 81c33b1a11..6a506118b8 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -588,18 +588,20 @@ impl MyWndProc { self.with_window_state(|s| s.is_resizable.set(resizable)); set_style(hwnd, resizable, self.has_titlebar()); } - DeferredOp::SetWindowState(val) => unsafe { - if self.handle.borrow().is_focusable() { - let s = match val { + DeferredOp::SetWindowState(val) => { + let show = if self.handle.borrow().is_focusable() { + match val { window::WindowState::Maximized => SW_MAXIMIZE, window::WindowState::Minimized => SW_MINIMIZE, window::WindowState::Restored => SW_RESTORE, - }; - ShowWindow(hwnd, s); + } } else { - ShowWindow(hwnd, SW_SHOWNOACTIVATE); + SW_SHOWNOACTIVATE + }; + unsafe { + ShowWindow(hwnd, show); } - }, + } DeferredOp::SaveAs(options, token) => { let info = unsafe { get_file_dialog_path(hwnd, FileDialogType::Save, options) @@ -1729,17 +1731,17 @@ impl WindowHandle { pub fn show(&self) { if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); - unsafe { - if w.is_focusable { - let show = match self.get_window_state() { - window::WindowState::Maximized => SW_MAXIMIZE, - window::WindowState::Minimized => SW_MINIMIZE, - _ => SW_SHOWNORMAL, - }; - ShowWindow(hwnd, show); - } else { - ShowWindow(hwnd, SW_SHOWNOACTIVATE); + let show = if w.is_focusable { + match self.get_window_state() { + window::WindowState::Maximized => SW_MAXIMIZE, + window::WindowState::Minimized => SW_MINIMIZE, + _ => SW_SHOWNORMAL, } + } else { + SW_SHOWNOACTIVATE + }; + unsafe { + ShowWindow(hwnd, show); UpdateWindow(hwnd); } }