Detailed changes
@@ -318,6 +318,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
) -> Option<oneshot::Receiver<usize>>;
fn activate(&self);
fn is_active(&self) -> bool;
+ fn is_hovered(&self) -> bool;
fn set_title(&mut self, title: &str);
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
fn minimize(&self);
@@ -327,6 +328,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
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_moved(&self, callback: Box<dyn FnMut()>);
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
@@ -1403,6 +1403,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if let Some(window) = get_window(&mut state, &surface.id()) {
state.mouse_focused_window = Some(window.clone());
+
if state.enter_token.is_some() {
state.enter_token = None;
}
@@ -1416,7 +1417,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
}
}
drop(state);
- window.set_focused(true);
+ window.set_hovered(true);
}
}
wl_pointer::Event::Leave { .. } => {
@@ -1432,7 +1433,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
drop(state);
focused_window.handle_input(input);
- focused_window.set_focused(false);
+ focused_window.set_hovered(false);
}
}
wl_pointer::Event::Motion {
@@ -36,6 +36,7 @@ pub(crate) struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(crate::PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>,
+ hover_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>,
@@ -97,6 +98,7 @@ pub struct WaylandWindowState {
client: WaylandClientStatePtr,
handle: AnyWindowHandle,
active: bool,
+ hovered: bool,
in_progress_configure: Option<InProgressConfigure>,
in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls,
@@ -181,6 +183,7 @@ impl WaylandWindowState {
appearance,
handle,
active: false,
+ hovered: false,
in_progress_window_controls: None,
// Assume that we can do anything, unless told otherwise
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) {
self.state.borrow_mut().appearance = appearance;
@@ -845,6 +854,10 @@ impl PlatformWindow for WaylandWindow {
self.borrow().active
}
+ fn is_hovered(&self) -> bool {
+ self.borrow().hovered
+ }
+
fn set_title(&mut self, title: &str) {
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);
}
+ 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)>) {
self.0.callbacks.borrow_mut().resize = Some(callback);
}
@@ -110,7 +110,8 @@ pub struct X11ClientState {
pub(crate) _resource_database: Database,
pub(crate) atoms: XcbAtoms,
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) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>,
@@ -144,7 +145,12 @@ impl X11ClientStatePtr {
if let Some(window_ref) = state.windows.remove(&x_window) {
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);
if state.windows.is_empty() {
@@ -341,7 +347,8 @@ impl X11Client {
_resource_database: resource_database,
atoms,
windows: HashMap::default(),
- focused_window: None,
+ mouse_focused_window: None,
+ keyboard_focused_window: None,
xkb: xkb_state,
ximc,
xim_handler,
@@ -502,7 +509,7 @@ impl X11Client {
.push(AttributeName::ClientWindow, xim_handler.window)
.push(AttributeName::FocusWindow, xim_handler.window);
- let window_id = state.focused_window;
+ let window_id = state.keyboard_focused_window;
drop(state);
if let Some(window_id) = window_id {
let window = self.get_window(window_id).unwrap();
@@ -586,17 +593,17 @@ impl X11Client {
}
Event::FocusIn(event) => {
let window = self.get_window(event.event)?;
- window.set_focused(true);
+ window.set_active(true);
let mut state = self.0.borrow_mut();
- state.focused_window = Some(event.event);
+ state.keyboard_focused_window = Some(event.event);
drop(state);
self.enable_ime();
}
Event::FocusOut(event) => {
let window = self.get_window(event.event)?;
- window.set_focused(false);
+ window.set_active(false);
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() {
compose_state.reset();
}
@@ -620,7 +627,7 @@ impl X11Client {
if state.modifiers == modifiers {
drop(state);
} else {
- let focused_window_id = state.focused_window?;
+ let focused_window_id = state.keyboard_focused_window?;
state.modifiers = modifiers;
drop(state);
@@ -871,12 +878,18 @@ impl X11Client {
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 => {
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;
- let window = self.get_window(event.event)?;
let mut state = self.0.borrow_mut();
+ state.mouse_focused_window = None;
let pressed_button = pressed_button_from_mask(event.buttons[0]);
let position = point(
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
@@ -886,11 +899,13 @@ impl X11Client {
state.modifiers = modifiers;
drop(state);
+ let window = self.get_window(event.event)?;
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button,
position,
modifiers,
}));
+ window.set_hovered(false);
}
_ => {}
};
@@ -1140,7 +1155,7 @@ impl LinuxClient for X11Client {
fn set_cursor_style(&self, style: CursorStyle) {
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;
};
let current_style = state
@@ -1272,7 +1287,7 @@ impl LinuxClient for X11Client {
fn active_window(&self) -> Option<AnyWindowHandle> {
let state = self.0.borrow();
- state.focused_window.and_then(|focused_window| {
+ state.keyboard_focused_window.and_then(|focused_window| {
state
.windows
.get(&focused_window)
@@ -211,6 +211,7 @@ pub struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
active_status_change: Option<Box<dyn FnMut(bool)>>,
+ hovered_status_change: Option<Box<dyn FnMut(bool)>>,
resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved: Option<Box<dyn FnMut()>>,
should_close: Option<Box<dyn FnMut() -> bool>>,
@@ -238,6 +239,7 @@ pub struct X11WindowState {
maximized_horizontal: bool,
hidden: bool,
active: bool,
+ hovered: bool,
fullscreen: bool,
client_side_decorations_supported: bool,
decorations: WindowDecorations,
@@ -451,6 +453,7 @@ impl X11WindowState {
xinput::XIEventMask::MOTION
| xinput::XIEventMask::BUTTON_PRESS
| xinput::XIEventMask::BUTTON_RELEASE
+ | xinput::XIEventMask::ENTER
| xinput::XIEventMask::LEAVE,
],
}],
@@ -507,6 +510,7 @@ impl X11WindowState {
atoms: *atoms,
input_handler: None,
active: false,
+ hovered: false,
fullscreen: false,
maximized_vertical: false,
maximized_horizontal: false,
@@ -777,6 +781,15 @@ impl X11WindowStatePtr {
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) {
@@ -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 {
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) {
let mut state = self.state.borrow_mut();
state.appearance = appearance;
@@ -1046,6 +1065,10 @@ impl PlatformWindow for X11Window {
self.0.state.borrow().active
}
+ fn is_hovered(&self) -> bool {
+ self.0.state.borrow().hovered
+ }
+
fn set_title(&mut self, title: &str) {
self.0
.xcb_connection
@@ -1162,6 +1185,10 @@ impl PlatformWindow for X11Window {
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)>) {
self.0.callbacks.borrow_mut().resize = Some(callback);
}
@@ -940,6 +940,11 @@ impl PlatformWindow for MacWindow {
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) {
unsafe {
let app = NSApplication::sharedApplication(nil);
@@ -1061,6 +1066,8 @@ impl PlatformWindow for MacWindow {
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)>) {
self.0.as_ref().lock().resize_callback = Some(callback);
}
@@ -23,6 +23,7 @@ pub(crate) struct TestWindowState {
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
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)>>,
moved_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<PlatformInputHandler>,
@@ -66,6 +67,7 @@ impl TestWindow {
should_close_handler: None,
input_callback: None,
active_status_change_callback: None,
+ hover_status_change_callback: None,
resize_callback: None,
moved_callback: None,
input_handler: None,
@@ -182,6 +184,10 @@ impl PlatformWindow for TestWindow {
false
}
+ fn is_hovered(&self) -> bool {
+ false
+ }
+
fn set_title(&mut self, title: &str) {
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)
}
+ 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)>) {
self.0.lock().resize_callback = Some(callback)
}
@@ -503,6 +503,11 @@ impl PlatformWindow for WindowsWindow {
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) {
unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
.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);
}
+ fn on_hover_status_change(&self, _: Box<dyn FnMut(bool)>) {}
+
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.state.borrow_mut().callbacks.resize = Some(callback);
}
@@ -541,6 +541,7 @@ pub struct Window {
appearance: WindowAppearance,
appearance_observers: SubscriberSet<(), AnyObserver>,
active: Rc<Cell<bool>>,
+ hovered: Rc<Cell<bool>>,
pub(crate) dirty: Rc<Cell<bool>>,
pub(crate) needs_present: Rc<Cell<bool>>,
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 dirty = Rc::new(Cell::new(true));
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 next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
@@ -778,7 +780,17 @@ impl Window {
.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({
let mut cx = cx.to_async();
Box::new(move |event| {
@@ -829,6 +841,7 @@ impl Window {
appearance,
appearance_observers: SubscriberSet::new(),
active,
+ hovered,
dirty,
needs_present,
last_input_timestamp,
@@ -1222,6 +1235,17 @@ impl<'a> WindowContext<'a> {
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.
pub fn zoom_window(&self) {
self.window.platform_window.zoom();
@@ -2980,7 +3004,7 @@ impl<'a> WindowContext<'a> {
fn reset_cursor_style(&self) {
// Set the cursor only if we're the active window.
- if self.is_window_active() {
+ if self.is_window_hovered() {
let style = self
.window
.rendered_frame