linux/x11: Restore differentiation of mouse/keyboard focus (#13995)

This restores https://github.com/zed-industries/zed/pull/13943 which was
reverted in #13974 because it was possible to get in a state where focus
could not be restored on a window.

In this PR there's an additional change: `FocusIn` and `FocusOut` events
are always handled, even if the `event.mode` is not "NORMAL". In my
testing, `alt-tabbing` between windows didn't produce `FocusIn` and
`FocusOut` events when we had that check. Now, with the check removed,
it's possible to switch focus between two windows again with `alt-tab`.


Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Thorsten Ball 2024-07-10 19:54:26 +02:00 committed by GitHub
parent c732865fc5
commit ee623f77c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 127 additions and 17 deletions

View file

@ -318,6 +318,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
) -> Option<oneshot::Receiver<usize>>; ) -> Option<oneshot::Receiver<usize>>;
fn activate(&self); fn activate(&self);
fn is_active(&self) -> bool; fn is_active(&self) -> bool;
fn is_hovered(&self) -> bool;
fn set_title(&mut self, title: &str); fn set_title(&mut self, title: &str);
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance); fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
fn minimize(&self); fn minimize(&self);
@ -327,6 +328,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_request_frame(&self, callback: Box<dyn FnMut()>); fn on_request_frame(&self, callback: Box<dyn FnMut()>);
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>); fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>); fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>); fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
fn on_moved(&self, callback: Box<dyn FnMut()>); fn on_moved(&self, callback: Box<dyn FnMut()>);
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>); fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);

View file

@ -1403,6 +1403,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if let Some(window) = get_window(&mut state, &surface.id()) { if let Some(window) = get_window(&mut state, &surface.id()) {
state.mouse_focused_window = Some(window.clone()); state.mouse_focused_window = Some(window.clone());
if state.enter_token.is_some() { if state.enter_token.is_some() {
state.enter_token = None; state.enter_token = None;
} }
@ -1416,7 +1417,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
} }
} }
drop(state); drop(state);
window.set_focused(true); window.set_hovered(true);
} }
} }
wl_pointer::Event::Leave { .. } => { wl_pointer::Event::Leave { .. } => {
@ -1432,7 +1433,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
drop(state); drop(state);
focused_window.handle_input(input); focused_window.handle_input(input);
focused_window.set_focused(false); focused_window.set_hovered(false);
} }
} }
wl_pointer::Event::Motion { wl_pointer::Event::Motion {

View file

@ -36,6 +36,7 @@ pub(crate) struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>, request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>, input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>, active_status_change: Option<Box<dyn FnMut(bool)>>,
hover_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>, resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>, moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>, should_close: Option<Box<dyn FnMut() -> bool>>,
@ -97,6 +98,7 @@ pub struct WaylandWindowState {
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
handle: AnyWindowHandle, handle: AnyWindowHandle,
active: bool, active: bool,
hovered: bool,
in_progress_configure: Option<InProgressConfigure>, in_progress_configure: Option<InProgressConfigure>,
in_progress_window_controls: Option<WindowControls>, in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls, window_controls: WindowControls,
@ -181,6 +183,7 @@ impl WaylandWindowState {
appearance, appearance,
handle, handle,
active: false, active: false,
hovered: false,
in_progress_window_controls: None, in_progress_window_controls: None,
// Assume that we can do anything, unless told otherwise // Assume that we can do anything, unless told otherwise
window_controls: WindowControls { window_controls: WindowControls {
@ -700,6 +703,12 @@ impl WaylandWindowStatePtr {
} }
} }
pub fn set_hovered(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().hover_status_change {
fun(focus);
}
}
pub fn set_appearance(&mut self, appearance: WindowAppearance) { pub fn set_appearance(&mut self, appearance: WindowAppearance) {
self.state.borrow_mut().appearance = appearance; self.state.borrow_mut().appearance = appearance;
@ -845,6 +854,10 @@ impl PlatformWindow for WaylandWindow {
self.borrow().active self.borrow().active
} }
fn is_hovered(&self) -> bool {
self.borrow().hovered
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.borrow().toplevel.set_title(title.to_string()); self.borrow().toplevel.set_title(title.to_string());
} }
@ -899,6 +912,10 @@ impl PlatformWindow for WaylandWindow {
self.0.callbacks.borrow_mut().active_status_change = Some(callback); self.0.callbacks.borrow_mut().active_status_change = Some(callback);
} }
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().hover_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) { fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.borrow_mut().resize = Some(callback); self.0.callbacks.borrow_mut().resize = Some(callback);
} }

View file

@ -110,7 +110,8 @@ pub struct X11ClientState {
pub(crate) _resource_database: Database, pub(crate) _resource_database: Database,
pub(crate) atoms: XcbAtoms, pub(crate) atoms: XcbAtoms,
pub(crate) windows: HashMap<xproto::Window, WindowRef>, pub(crate) windows: HashMap<xproto::Window, WindowRef>,
pub(crate) focused_window: Option<xproto::Window>, pub(crate) mouse_focused_window: Option<xproto::Window>,
pub(crate) keyboard_focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State, pub(crate) xkb: xkbc::State,
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>, pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>, pub(crate) xim_handler: Option<XimHandler>,
@ -144,7 +145,12 @@ impl X11ClientStatePtr {
if let Some(window_ref) = state.windows.remove(&x_window) { if let Some(window_ref) = state.windows.remove(&x_window) {
state.loop_handle.remove(window_ref.refresh_event_token); state.loop_handle.remove(window_ref.refresh_event_token);
} }
if state.mouse_focused_window == Some(x_window) {
state.mouse_focused_window = None;
}
if state.keyboard_focused_window == Some(x_window) {
state.keyboard_focused_window = None;
}
state.cursor_styles.remove(&x_window); state.cursor_styles.remove(&x_window);
if state.windows.is_empty() { if state.windows.is_empty() {
@ -341,7 +347,8 @@ impl X11Client {
_resource_database: resource_database, _resource_database: resource_database,
atoms, atoms,
windows: HashMap::default(), windows: HashMap::default(),
focused_window: None, mouse_focused_window: None,
keyboard_focused_window: None,
xkb: xkb_state, xkb: xkb_state,
ximc, ximc,
xim_handler, xim_handler,
@ -502,7 +509,7 @@ impl X11Client {
.push(AttributeName::ClientWindow, xim_handler.window) .push(AttributeName::ClientWindow, xim_handler.window)
.push(AttributeName::FocusWindow, xim_handler.window); .push(AttributeName::FocusWindow, xim_handler.window);
let window_id = state.focused_window; let window_id = state.keyboard_focused_window;
drop(state); drop(state);
if let Some(window_id) = window_id { if let Some(window_id) = window_id {
let window = self.get_window(window_id).unwrap(); let window = self.get_window(window_id).unwrap();
@ -586,17 +593,17 @@ impl X11Client {
} }
Event::FocusIn(event) => { Event::FocusIn(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
window.set_focused(true); window.set_active(true);
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.focused_window = Some(event.event); state.keyboard_focused_window = Some(event.event);
drop(state); drop(state);
self.enable_ime(); self.enable_ime();
} }
Event::FocusOut(event) => { Event::FocusOut(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
window.set_focused(false); window.set_active(false);
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.focused_window = None; state.keyboard_focused_window = None;
if let Some(compose_state) = state.compose_state.as_mut() { if let Some(compose_state) = state.compose_state.as_mut() {
compose_state.reset(); compose_state.reset();
} }
@ -620,7 +627,7 @@ impl X11Client {
if state.modifiers == modifiers { if state.modifiers == modifiers {
drop(state); drop(state);
} else { } else {
let focused_window_id = state.focused_window?; let focused_window_id = state.keyboard_focused_window?;
state.modifiers = modifiers; state.modifiers = modifiers;
drop(state); drop(state);
@ -871,12 +878,18 @@ impl X11Client {
valuator_idx += 1; valuator_idx += 1;
} }
} }
Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => {
let window = self.get_window(event.event)?;
window.set_hovered(true);
let mut state = self.0.borrow_mut();
state.mouse_focused_window = Some(event.event);
}
Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => { Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global) self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
self.0.borrow_mut().scroll_y = None; self.0.borrow_mut().scroll_y = None;
let window = self.get_window(event.event)?;
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.mouse_focused_window = None;
let pressed_button = pressed_button_from_mask(event.buttons[0]); let pressed_button = pressed_button_from_mask(event.buttons[0]);
let position = point( let position = point(
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor), px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
@ -886,11 +899,13 @@ impl X11Client {
state.modifiers = modifiers; state.modifiers = modifiers;
drop(state); drop(state);
let window = self.get_window(event.event)?;
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent { window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button, pressed_button,
position, position,
modifiers, modifiers,
})); }));
window.set_hovered(false);
} }
_ => {} _ => {}
}; };
@ -1140,7 +1155,7 @@ impl LinuxClient for X11Client {
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let Some(focused_window) = state.focused_window else { let Some(focused_window) = state.mouse_focused_window else {
return; return;
}; };
let current_style = state let current_style = state
@ -1272,7 +1287,7 @@ impl LinuxClient for X11Client {
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
let state = self.0.borrow(); let state = self.0.borrow();
state.focused_window.and_then(|focused_window| { state.keyboard_focused_window.and_then(|focused_window| {
state state
.windows .windows
.get(&focused_window) .get(&focused_window)

View file

@ -211,6 +211,7 @@ pub struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>, request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>, input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>, active_status_change: Option<Box<dyn FnMut(bool)>>,
hovered_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>, resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>, moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>, should_close: Option<Box<dyn FnMut() -> bool>>,
@ -238,6 +239,7 @@ pub struct X11WindowState {
maximized_horizontal: bool, maximized_horizontal: bool,
hidden: bool, hidden: bool,
active: bool, active: bool,
hovered: bool,
fullscreen: bool, fullscreen: bool,
client_side_decorations_supported: bool, client_side_decorations_supported: bool,
decorations: WindowDecorations, decorations: WindowDecorations,
@ -451,6 +453,7 @@ impl X11WindowState {
xinput::XIEventMask::MOTION xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS | xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE | xinput::XIEventMask::BUTTON_RELEASE
| xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE, | xinput::XIEventMask::LEAVE,
], ],
}], }],
@ -507,6 +510,7 @@ impl X11WindowState {
atoms: *atoms, atoms: *atoms,
input_handler: None, input_handler: None,
active: false, active: false,
hovered: false,
fullscreen: false, fullscreen: false,
maximized_vertical: false, maximized_vertical: false,
maximized_horizontal: false, maximized_horizontal: false,
@ -777,6 +781,15 @@ impl X11WindowStatePtr {
state.hidden = true; state.hidden = true;
} }
} }
let hovered_window = self
.xcb_connection
.query_pointer(state.x_root_window)
.unwrap()
.reply()
.unwrap()
.child;
self.set_hovered(hovered_window == self.x_window);
} }
pub fn close(&self) { pub fn close(&self) {
@ -912,12 +925,18 @@ impl X11WindowStatePtr {
} }
} }
pub fn set_focused(&self, focus: bool) { pub fn set_active(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change { if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
fun(focus); fun(focus);
} }
} }
pub fn set_hovered(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.borrow_mut().hovered_status_change {
fun(focus);
}
}
pub fn set_appearance(&mut self, appearance: WindowAppearance) { pub fn set_appearance(&mut self, appearance: WindowAppearance) {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
state.appearance = appearance; state.appearance = appearance;
@ -1046,6 +1065,10 @@ impl PlatformWindow for X11Window {
self.0.state.borrow().active self.0.state.borrow().active
} }
fn is_hovered(&self) -> bool {
self.0.state.borrow().hovered
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.0 self.0
.xcb_connection .xcb_connection
@ -1162,6 +1185,10 @@ impl PlatformWindow for X11Window {
self.0.callbacks.borrow_mut().active_status_change = Some(callback); self.0.callbacks.borrow_mut().active_status_change = Some(callback);
} }
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) { fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.borrow_mut().resize = Some(callback); self.0.callbacks.borrow_mut().resize = Some(callback);
} }

View file

@ -940,6 +940,11 @@ impl PlatformWindow for MacWindow {
unsafe { self.0.lock().native_window.isKeyWindow() == YES } unsafe { self.0.lock().native_window.isKeyWindow() == YES }
} }
// is_hovered is unused on macOS. See WindowContext::is_window_hovered.
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
unsafe { unsafe {
let app = NSApplication::sharedApplication(nil); let app = NSApplication::sharedApplication(nil);
@ -1061,6 +1066,8 @@ impl PlatformWindow for MacWindow {
self.0.as_ref().lock().activate_callback = Some(callback); self.0.as_ref().lock().activate_callback = Some(callback);
} }
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) { fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.as_ref().lock().resize_callback = Some(callback); self.0.as_ref().lock().resize_callback = Some(callback);
} }

View file

@ -23,6 +23,7 @@ pub(crate) struct TestWindowState {
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>, pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>, input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
active_status_change_callback: Option<Box<dyn FnMut(bool)>>, active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>, resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>, moved_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<PlatformInputHandler>, input_handler: Option<PlatformInputHandler>,
@ -66,6 +67,7 @@ impl TestWindow {
should_close_handler: None, should_close_handler: None,
input_callback: None, input_callback: None,
active_status_change_callback: None, active_status_change_callback: None,
hover_status_change_callback: None,
resize_callback: None, resize_callback: None,
moved_callback: None, moved_callback: None,
input_handler: None, input_handler: None,
@ -182,6 +184,10 @@ impl PlatformWindow for TestWindow {
false false
} }
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.0.lock().title = Some(title.to_owned()); self.0.lock().title = Some(title.to_owned());
} }
@ -225,6 +231,10 @@ impl PlatformWindow for TestWindow {
self.0.lock().active_status_change_callback = Some(callback) self.0.lock().active_status_change_callback = Some(callback)
} }
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.lock().hover_status_change_callback = Some(callback)
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) { fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.lock().resize_callback = Some(callback) self.0.lock().resize_callback = Some(callback)
} }

View file

@ -503,6 +503,11 @@ impl PlatformWindow for WindowsWindow {
self.0.hwnd == unsafe { GetActiveWindow() } self.0.hwnd == unsafe { GetActiveWindow() }
} }
// is_hovered is unused on Windows. See WindowContext::is_window_hovered.
fn is_hovered(&self) -> bool {
false
}
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) } unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
.inspect_err(|e| log::error!("Set title failed: {e}")) .inspect_err(|e| log::error!("Set title failed: {e}"))
@ -604,6 +609,8 @@ impl PlatformWindow for WindowsWindow {
self.0.state.borrow_mut().callbacks.active_status_change = Some(callback); self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
} }
fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) { fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.state.borrow_mut().callbacks.resize = Some(callback); self.0.state.borrow_mut().callbacks.resize = Some(callback);
} }

View file

@ -541,6 +541,7 @@ pub struct Window {
appearance: WindowAppearance, appearance: WindowAppearance,
appearance_observers: SubscriberSet<(), AnyObserver>, appearance_observers: SubscriberSet<(), AnyObserver>,
active: Rc<Cell<bool>>, active: Rc<Cell<bool>>,
hovered: Rc<Cell<bool>>,
pub(crate) dirty: Rc<Cell<bool>>, pub(crate) dirty: Rc<Cell<bool>>,
pub(crate) needs_present: Rc<Cell<bool>>, pub(crate) needs_present: Rc<Cell<bool>>,
pub(crate) last_input_timestamp: Rc<Cell<Instant>>, pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
@ -672,6 +673,7 @@ impl Window {
let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone())); let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
let dirty = Rc::new(Cell::new(true)); let dirty = Rc::new(Cell::new(true));
let active = Rc::new(Cell::new(platform_window.is_active())); let active = Rc::new(Cell::new(platform_window.is_active()));
let hovered = Rc::new(Cell::new(platform_window.is_hovered()));
let needs_present = Rc::new(Cell::new(false)); let needs_present = Rc::new(Cell::new(false));
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default(); let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let last_input_timestamp = Rc::new(Cell::new(Instant::now())); let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
@ -778,7 +780,17 @@ impl Window {
.log_err(); .log_err();
} }
})); }));
platform_window.on_hover_status_change(Box::new({
let mut cx = cx.to_async();
move |active| {
handle
.update(&mut cx, |_, cx| {
cx.window.hovered.set(active);
cx.refresh();
})
.log_err();
}
}));
platform_window.on_input({ platform_window.on_input({
let mut cx = cx.to_async(); let mut cx = cx.to_async();
Box::new(move |event| { Box::new(move |event| {
@ -829,6 +841,7 @@ impl Window {
appearance, appearance,
appearance_observers: SubscriberSet::new(), appearance_observers: SubscriberSet::new(),
active, active,
hovered,
dirty, dirty,
needs_present, needs_present,
last_input_timestamp, last_input_timestamp,
@ -1222,6 +1235,17 @@ impl<'a> WindowContext<'a> {
self.window.active.get() self.window.active.get()
} }
/// Returns whether this window is considered to be the window
/// that currently owns the mouse cursor.
/// On mac, this is equivalent to `is_window_active`.
pub fn is_window_hovered(&self) -> bool {
if cfg!(target_os = "linux") {
self.window.hovered.get()
} else {
self.is_window_active()
}
}
/// Toggle zoom on the window. /// Toggle zoom on the window.
pub fn zoom_window(&self) { pub fn zoom_window(&self) {
self.window.platform_window.zoom(); self.window.platform_window.zoom();
@ -2980,7 +3004,7 @@ impl<'a> WindowContext<'a> {
fn reset_cursor_style(&self) { fn reset_cursor_style(&self) {
// Set the cursor only if we're the active window. // Set the cursor only if we're the active window.
if self.is_window_active() { if self.is_window_hovered() {
let style = self let style = self
.window .window
.rendered_frame .rendered_frame