Skip to content

Commit

Permalink
Implement tooltip level on Windows platform (#1737)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
djeedai committed May 2, 2021
1 parent f0e36f9 commit 2ab1d75
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 21 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -56,13 +58,15 @@ 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])
- Use correct fill rule when rendering SVG paths ([#1606] by [@SecondFlight])
- 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

Expand Down Expand Up @@ -463,6 +467,7 @@ Last release without a changelog :(
[@ccqpein]: https://github.com/ccqpein
[@RichardPoole42]: https://github.com/RichardPoole42
[@r-ml]: https://github.com/r-ml
[@djeedai]: https://github.com/djeedai

[#599]: https://github.com/linebender/druid/pull/599
[#611]: https://github.com/linebender/druid/pull/611
Expand Down Expand Up @@ -692,6 +697,7 @@ Last release without a changelog :(
[#1722]: https://github.com/linebender/druid/pull/1722
[#1724]: https://github.com/linebender/druid/pull/1724
[#1730]: https://github.com/linebender/druid/pull/1730
[#1737]: https://github.com/linebender/druid/pull/1737
[#1743]: https://github.com/linebender/druid/pull/1743

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
Expand Down
100 changes: 80 additions & 20 deletions druid-shell/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub(crate) struct WindowBuilder {
transparent: bool,
min_size: Option<Size>,
position: Option<Point>,
level: Option<WindowLevel>,
state: window::WindowState,
}

Expand Down Expand Up @@ -223,6 +224,9 @@ struct WindowState {
is_resizable: Cell<bool>,
handle_titlebar: Cell<bool>,
active_text_input: Cell<Option<TextFieldToken>>,
// 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.
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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!(
Expand All @@ -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!(
Expand All @@ -578,14 +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 {
let s = match val {
window::WindowState::Maximized => SW_MAXIMIZE,
window::WindowState::Minimized => SW_MINIMIZE,
window::WindowState::Restored => SW_RESTORE,
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,
}
} else {
SW_SHOWNOACTIVATE
};
ShowWindow(hwnd, s);
},
unsafe {
ShowWindow(hwnd, show);
}
}
DeferredOp::SaveAs(options, token) => {
let info = unsafe {
get_file_dialog_path(hwnd, FileDialogType::Save, options)
Expand Down Expand Up @@ -747,7 +763,9 @@ impl WndProc for MyWndProc {
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_FRAMECHANGED
| SWP_NOSIZE,
| SWP_NOSIZE
| SWP_NOOWNERZORDER
| SWP_NOACTIVATE,
) == 0
{
warn!(
Expand Down Expand Up @@ -813,7 +831,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)
},
Expand Down Expand Up @@ -1233,6 +1255,7 @@ impl WindowBuilder {
size: None,
min_size: None,
position: None,
level: None,
state: window::WindowState::Restored,
}
}
Expand Down Expand Up @@ -1287,8 +1310,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<WindowHandle, Error> {
Expand Down Expand Up @@ -1330,6 +1358,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),
Expand All @@ -1345,6 +1395,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 {
Expand All @@ -1367,14 +1418,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;
}
Expand Down Expand Up @@ -1413,7 +1463,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!(
Expand Down Expand Up @@ -1681,12 +1731,16 @@ impl WindowHandle {
pub fn show(&self) {
if let Some(w) = self.state.upgrade() {
let hwnd = w.hwnd.get();
unsafe {
let show = match self.get_window_state() {
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);
}
Expand Down Expand Up @@ -2061,6 +2115,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<IdleHandle> {
self.state.upgrade().map(|w| IdleHandle {
Expand Down

0 comments on commit 2ab1d75

Please sign in to comment.