Skip to content

Commit

Permalink
Add set_mouse_pass_through and is_foreground_window (#2402)
Browse files Browse the repository at this point in the history
- `set_mouse_pass_through` sets whether the mouse passes through the
window to whatever is behind.
- `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.
  • Loading branch information
AlexKnauth authored Oct 22, 2024
1 parent 4fe3939 commit e7f49e7
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 2 deletions.
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

0 comments on commit e7f49e7

Please sign in to comment.