Skip to content

Commit

Permalink
Add Event::MouseLeave for windows (#821)
Browse files Browse the repository at this point in the history
* Add windows specific mouse_leave event on druid shell
The event notifies the app that the cursor is no longer hovering the
application window.

* Add MouseLeft event to druid
When the MouseLeft is propagated, druid automatically clears is_hot.

* Handle error for TrackMouseEvent

* Rename MouseLeft -> MouseLeave

* Fix comment in WM_MOUSEMOVE

* Some smaller changes from code review

* Only set has_mouse_focus when TrackMouseEvent succeeds.
  • Loading branch information
teddemunnik committed Apr 13, 2020
1 parent 90965bb commit f3a755f
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 0 deletions.
53 changes: 53 additions & 0 deletions druid-shell/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ struct WndState {
// capture. When the first mouse button is down on our window we enter
// capture, and we hold it until the last mouse button is up.
captured_mouse_buttons: u32,
// Is this window the topmost window under the mouse cursor
has_mouse_focus: bool,
//TODO: track surrogate orphan
}

Expand Down Expand Up @@ -229,6 +231,22 @@ fn get_mod_state() -> KeyModifiers {
}
}

fn is_point_in_client_rect(hwnd: HWND, x: i32, y: i32) -> bool {
unsafe {
let mut client_rect = mem::MaybeUninit::uninit();
if GetClientRect(hwnd, client_rect.as_mut_ptr()) == FALSE {
warn!(
"failed to get client rect: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
return false;
}
let client_rect = client_rect.assume_init();
let mouse_point = POINT { x, y };
PtInRect(&client_rect, mouse_point) != FALSE
}
}

impl WndState {
fn rebuild_render_target(&mut self, d2d: &D2DFactory) {
unsafe {
Expand Down Expand Up @@ -587,6 +605,30 @@ impl WndProc for MyWndProc {
let s = s.as_mut().unwrap();
let x = LOWORD(lparam as u32) as i16 as i32;
let y = HIWORD(lparam as u32) as i16 as i32;

// When the mouse first enters the window client rect we need to register for the
// WM_MOUSELEAVE event. Note that WM_MOUSEMOVE is also called even when the
// window under the cursor changes without moving the mouse, for example when
// our window is first opened under the mouse cursor.
if !s.has_mouse_focus && is_point_in_client_rect(hwnd, x, y) {
let mut desc = TRACKMOUSEEVENT {
cbSize: mem::size_of::<TRACKMOUSEEVENT>() as DWORD,
dwFlags: TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: HOVER_DEFAULT,
};
unsafe {
if TrackMouseEvent(&mut desc) != FALSE {
s.has_mouse_focus = true;
} else {
warn!(
"failed to TrackMouseEvent: {}",
Error::Hr(HRESULT_FROM_WIN32(GetLastError()))
);
}
}
}

let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y);
let pos = Point::new(px as f64, py as f64);
let mods = get_mod_state();
Expand All @@ -612,6 +654,16 @@ impl WndProc for MyWndProc {
}
Some(0)
}
WM_MOUSELEAVE => {
if let Ok(mut s) = self.state.try_borrow_mut() {
let s = s.as_mut().unwrap();
s.has_mouse_focus = false;
s.handler.mouse_leave();
} else {
self.log_dropped_msg(hwnd, msg, wparam, lparam);
}
Some(0)
}
// TODO: not clear where double-click processing should happen. Currently disabled
// because CS_DBLCLKS is not set
WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDBLCLK
Expand Down Expand Up @@ -846,6 +898,7 @@ impl WindowBuilder {
stashed_key_code: KeyCode::Unknown(0),
stashed_char: None,
captured_mouse_buttons: 0,
has_mouse_focus: false,
};
win.wndproc.connect(&handle, state);

Expand Down
3 changes: 3 additions & 0 deletions druid-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ pub trait WinHandler {
#[allow(unused_variables)]
fn mouse_up(&mut self, event: &MouseEvent) {}

/// Called when the mouse cursor has left the application window
fn mouse_leave(&mut self) {}

/// Called on timer event.
///
/// This is called at (approximately) the requested deadline by a
Expand Down
9 changes: 9 additions & 0 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
mouse_event.pos -= rect.origin().to_vec2();
Event::MouseMove(mouse_event)
}
Event::MouseLeave => {
let had_hot = child_ctx.base_state.is_hot;
child_ctx.base_state.is_hot = false;
if had_hot {
hot_changed = Some(false);
}
recurse = had_active || had_hot;
Event::MouseLeave
}
Event::KeyDown(e) => {
recurse = child_ctx.has_focus();
Event::KeyDown(*e)
Expand Down
4 changes: 4 additions & 0 deletions druid/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ pub enum Event {
///
/// [`set_cursor`]: struct.EventCtx.html#method.set_cursor
MouseMove(MouseEvent),
/// Called when the mouse has left the window.
///
/// The `MouseLeave` event is propagated to both active and hot widgets.
MouseLeave,
/// Called when a key is pressed.
///
/// Note: the intent is for each physical key press to correspond to
Expand Down
5 changes: 5 additions & 0 deletions druid/src/win_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,11 @@ impl<T: Data> WinHandler for DruidHandler<T> {
self.app_state.do_window_event(event, self.window_id);
}

fn mouse_leave(&mut self) {
self.app_state
.do_window_event(Event::MouseLeave, self.window_id);
}

fn key_down(&mut self, event: KeyEvent) -> bool {
self.app_state
.do_window_event(Event::KeyDown(event), self.window_id)
Expand Down

0 comments on commit f3a755f

Please sign in to comment.