Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add set_mouse_pass_through and is_foreground_window #2402

Merged
merged 9 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ You can find its changes [documented below](#083---2023-02-28).
### Added

- Type name is now included in panic error messages in `WidgetPod`. ([#2380] by [@matthewgapp])
- `set_mouse_pass_through` sets whether the mouse passes through the window to whatever is behind. ([#2402] by [@AlexKnauth])
- `is_foreground_window` returns true if the window is the foreground window or this is unknown, and returns false if a different window is known to be the foreground window. ([#2402] by [@AlexKnauth])

### Changed

Expand Down Expand Up @@ -790,6 +792,7 @@ Last release without a changelog :(
[@AtomicGamer9523]: https://github.com/AtomicGamer9523
[@Insprill]: https://github.com/Insprill
[@matthewgapp]: https://github.com/matthewgapp
[@AlexKnauth]: https://github.com/AlexKnauth

[#599]: https://github.com/linebender/druid/pull/599
[#611]: https://github.com/linebender/druid/pull/611
Expand Down Expand Up @@ -1237,6 +1240,7 @@ Last release without a changelog :(
[#2375]: https://github.com/linebender/druid/pull/2375
[#2378]: https://github.com/linebender/druid/pull/2378
[#2380]: https://github.com/linebender/druid/pull/2380
[#2402]: https://github.com/linebender/druid/pull/2402

[Unreleased]: https://github.com/linebender/druid/compare/v0.8.3...master
[0.8.3]: https://github.com/linebender/druid/compare/v0.8.2...v0.8.3
Expand Down
8 changes: 8 additions & 0 deletions druid-shell/src/backend/gtk/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,10 @@ impl WindowHandle {
}
}

pub fn is_foreground_window(&self) -> bool {
true
}

pub fn set_window_state(&mut self, size_state: window::WindowState) {
use window::WindowState::{Maximized, Minimized, Restored};
let cur_size_state = self.get_window_state();
Expand Down Expand Up @@ -1122,6 +1126,10 @@ impl WindowHandle {
}
}

pub fn set_mouse_pass_through(&self, _mouse_pass_thorugh: bool) {
warn!("set_mouse_pass_through unimplemented");
}

pub fn handle_titlebar(&self, val: bool) {
if let Some(state) = self.state.upgrade() {
state.handle_titlebar.set(val);
Expand Down
15 changes: 15 additions & 0 deletions druid-shell/src/backend/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,13 @@ impl WindowHandle {
}
}

pub fn set_mouse_pass_through(&self, mouse_pass_through: bool) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
window.setIgnoresMouseEvents_(mouse_pass_through as BOOL);
}
}

fn set_level(&self, level: WindowLevel) {
unsafe {
let level = levels::as_raw_window_level(level);
Expand All @@ -1355,6 +1362,14 @@ impl WindowHandle {
}
}

pub fn is_foreground_window(&self) -> bool {
unsafe {
let application: id = msg_send![class![NSRunningApplication], currentApplication];
let is_active: BOOL = msg_send![application, isActive];
is_active != NO
}
}

pub fn get_window_state(&self) -> WindowState {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
Expand Down
8 changes: 8 additions & 0 deletions druid-shell/src/backend/wayland/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ impl WindowHandle {
tracing::warn!("set_always_on_top is unimplemented on wayland");
}

pub fn set_mouse_pass_through(&self, _mouse_pass_thorugh: bool) {
tracing::warn!("set_mouse_pass_through unimplemented");
}

pub fn set_input_region(&self, region: Option<Region>) {
self.inner.surface.set_input_region(region);
}
Expand All @@ -130,6 +134,10 @@ impl WindowHandle {
self.inner.surface.get_size()
}

pub fn is_foreground_window(&self) -> bool {
true
}

pub fn set_window_state(&mut self, _current_state: window::WindowState) {
tracing::warn!("set_window_state is unimplemented on wayland");
}
Expand Down
8 changes: 8 additions & 0 deletions druid-shell/src/backend/web/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,10 @@ impl WindowHandle {
warn!("WindowHandle::set_always_on_top unimplemented for web");
}

pub fn set_mouse_pass_through(&self, _mouse_pass_thorugh: bool) {
warn!("WindowHandle::set_mouse_pass_through unimplemented for web");
}

pub fn get_position(&self) -> Point {
warn!("WindowHandle::get_position unimplemented for web.");
Point::new(0.0, 0.0)
Expand All @@ -524,6 +528,10 @@ impl WindowHandle {
Size::new(0.0, 0.0)
}

pub fn is_foreground_window(&self) -> bool {
true
}

pub fn content_insets(&self) -> Insets {
warn!("WindowHandle::content_insets unimplemented for web.");
Insets::ZERO
Expand Down
53 changes: 53 additions & 0 deletions druid-shell/src/backend/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub(crate) struct WindowBuilder {
position: Option<Point>,
level: Option<WindowLevel>,
always_on_top: bool,
mouse_pass_through: bool,
state: window::WindowState,
}

Expand Down Expand Up @@ -156,6 +157,7 @@ enum DeferredOp {
ReleaseMouseCapture,
SetRegion(Option<Region>),
SetAlwaysOnTop(bool),
SetMousePassThrough(bool),
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -232,6 +234,7 @@ struct WindowState {
is_focusable: bool,
window_level: WindowLevel,
is_always_on_top: Cell<bool>,
is_mouse_pass_through: Cell<bool>,
}

impl std::fmt::Debug for WindowState {
Expand Down Expand Up @@ -464,6 +467,38 @@ fn set_ex_style(hwnd: HWND, always_on_top: bool) {
}
}

fn set_mouse_pass_through(hwnd: HWND, mouse_pass_through: bool) {
unsafe {
let mut style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE) as u32;
if style == 0 {
warn!(
"failed to get window ex style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return;
}

if !mouse_pass_through {
// Not removing WS_EX_LAYERED because it may still be needed if Opacity != 1.
style &= !WS_EX_TRANSPARENT;
} else if (style & (WS_EX_LAYERED | WS_EX_TRANSPARENT))
!= (WS_EX_LAYERED | WS_EX_TRANSPARENT)
{
// We have to add WS_EX_LAYERED, because WS_EX_TRANSPARENT won't work otherwise.
style |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
} else {
// nothing to do
return;
}
if SetWindowLongPtrW(hwnd, GWL_EXSTYLE, style as _) == 0 {
warn!(
"failed to set the window ex style: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}

impl WndState {
fn rebuild_render_target(&mut self, d2d: &D2DFactory, scale: Scale) -> Result<(), Error> {
unsafe {
Expand Down Expand Up @@ -647,6 +682,10 @@ impl MyWndProc {
self.with_window_state(|s| s.is_always_on_top.set(always_on_top));
set_ex_style(hwnd, always_on_top);
}
DeferredOp::SetMousePassThrough(mouse_pass_through) => {
self.with_window_state(|s| s.is_mouse_pass_through.set(mouse_pass_through));
set_mouse_pass_through(hwnd, mouse_pass_through);
}
DeferredOp::SetWindowState(val) => {
let show = if self.handle.borrow().is_focusable() {
match val {
Expand Down Expand Up @@ -1490,6 +1529,7 @@ impl WindowBuilder {
position: None,
level: None,
always_on_top: false,
mouse_pass_through: false,
state: window::WindowState::Restored,
}
}
Expand Down Expand Up @@ -1640,6 +1680,7 @@ impl WindowBuilder {
is_focusable: focusable,
window_level,
is_always_on_top: Cell::new(self.always_on_top),
is_mouse_pass_through: Cell::new(self.mouse_pass_through),
};
let win = Rc::new(window);
let handle = WindowHandle {
Expand Down Expand Up @@ -2244,6 +2285,14 @@ impl WindowHandle {
Size::new(0.0, 0.0)
}

pub fn is_foreground_window(&self) -> bool {
let Some(w) = self.state.upgrade() else {
return true;
};
let hwnd = w.hwnd.get();
unsafe { GetForegroundWindow() == hwnd }
}

pub fn set_input_region(&self, area: Option<Region>) {
self.defer(DeferredOp::SetRegion(area));
}
Expand All @@ -2252,6 +2301,10 @@ impl WindowHandle {
self.defer(DeferredOp::SetAlwaysOnTop(always_on_top));
}

pub fn set_mouse_pass_through(&self, mouse_pass_through: bool) {
self.defer(DeferredOp::SetMousePassThrough(mouse_pass_through));
}

pub fn resizable(&self, resizable: bool) {
self.defer(DeferredOp::SetResizable(resizable));
}
Expand Down
8 changes: 8 additions & 0 deletions druid-shell/src/backend/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,10 @@ impl WindowHandle {
}
}

pub fn set_mouse_pass_through(&self, _mouse_pass_thorugh: bool) {
warn!("set_mouse_pass_through unimplemented");
}

pub fn set_input_region(&self, region: Option<Region>) {
if let Some(w) = self.window.upgrade() {
w.set_input_region(region);
Expand Down Expand Up @@ -1714,6 +1718,10 @@ impl WindowHandle {
}
}

pub fn is_foreground_window(&self) -> bool {
true
}

pub fn set_window_state(&self, _state: window::WindowState) {
warn!("WindowHandle::set_window_state is currently unimplemented for X11 backend.");
}
Expand Down
11 changes: 11 additions & 0 deletions druid-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ impl WindowHandle {
self.0.set_always_on_top(always_on_top);
}

/// Sets whether the mouse passes through the window to whatever is behind.
pub fn set_mouse_pass_through(&self, mouse_pass_through: bool) {
self.0.set_mouse_pass_through(mouse_pass_through);
}

/// Sets where in the window the user can interact with the program.
///
/// This enables irregularly shaped windows. For example, you can make it simply
Expand All @@ -244,6 +249,12 @@ impl WindowHandle {
self.0.set_input_region(region)
}

/// Returns true if the window is the foreground window or this is unknown.
/// Returns false if a different window is known to be the foreground window.
pub fn is_foreground_window(&self) -> bool {
self.0.is_foreground_window()
}

/// Returns the position of the top left corner of the window.
///
/// The position is returned in [display points], measured relative to the parent window if
Expand Down
25 changes: 23 additions & 2 deletions druid/examples/input_region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ struct AppState {
limit_input_region: bool,
show_titlebar: bool,
always_on_top: bool,
mouse_pass_through_while_not_in_focus: bool,
mouse_pass_through: bool,
}

struct InputRegionExampleWidget {
Expand All @@ -38,7 +40,7 @@ impl InputRegionExampleWidget {
let info_label = Label::new(INFO_TEXT)
.with_line_break_mode(LineBreaking::WordWrap)
.padding(20.0)
.background(Color::rgba(0.2, 0.2, 0.2, 1.0));
.background(Color::rgba(0.2, 0.2, 0.2, 0.5));
let toggle_input_region = Button::new("Toggle Input Region")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
Expand All @@ -61,10 +63,20 @@ impl InputRegionExampleWidget {
ctx.window().set_always_on_top(*data);
})
.lens(AppState::always_on_top);
let toggle_mouse_pass_through_while_not_in_focus = Button::new("Toggle Mouse Pass Through")
.on_click(|_, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!(
"Setting mouse pass through while not in focus to: {}",
*data
);
})
.lens(AppState::mouse_pass_through_while_not_in_focus);
let controls_flex = Flex::row()
.with_child(toggle_input_region)
.with_child(toggle_titlebar)
.with_child(toggle_always_on_top);
.with_child(toggle_always_on_top)
.with_child(toggle_mouse_pass_through_while_not_in_focus);
Self {
info_label: WidgetPod::new(info_label),
controls: WidgetPod::new(controls_flex),
Expand All @@ -82,6 +94,13 @@ impl Widget<AppState> for InputRegionExampleWidget {
) {
self.info_label.event(ctx, event, data, env);
self.controls.event(ctx, event, data, env);
let mouse_pass_through =
data.mouse_pass_through_while_not_in_focus && !ctx.window().is_foreground_window();
if mouse_pass_through != data.mouse_pass_through {
data.mouse_pass_through = mouse_pass_through;
tracing::debug!("Setting mouse pass through to: {}", mouse_pass_through);
ctx.window().set_mouse_pass_through(mouse_pass_through);
}
}

fn lifecycle(
Expand Down Expand Up @@ -196,6 +215,8 @@ fn main() {
limit_input_region: true,
always_on_top: false,
show_titlebar: false,
mouse_pass_through_while_not_in_focus: false,
mouse_pass_through: false,
};

AppLauncher::with_window(main_window)
Expand Down
Loading