From 2d29bfc89ef9083c240c46d8f59c770b0f08cedd Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 21 Feb 2021 11:56:59 -0700 Subject: [PATCH] WindowServer: Re-evaluate the mouse cursor when alpha hit-testing A window repaint may change the alpha value, resulting in a different hit test outcome. In those cases, re-evaluate the cursor hit testing after a window was painted, and update the cursor if needed. --- .../WindowServer/ClientConnection.cpp | 5 +- Userland/Services/WindowServer/Compositor.cpp | 4 +- .../Services/WindowServer/WindowManager.cpp | 51 ++++++++++++++++++- .../Services/WindowServer/WindowManager.h | 5 +- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index 2626d9c4fb..7347eb8a78 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -603,6 +603,8 @@ void ClientConnection::handle(const Messages::WindowServer::DidFinishPainting& m auto& window = *(*it).value; for (auto& rect : message.rects()) window.invalidate(rect); + if (window.has_alpha_channel() && window.alpha_hit_threshold() > 0.0) + WindowManager::the().reevaluate_hovered_window(&window); WindowSwitcher::the().refresh_if_needed(); } @@ -661,7 +663,8 @@ OwnPtr ClientConnection::handle return {}; } window.set_cursor(Cursor::create((Gfx::StandardCursor)message.cursor_type())); - Compositor::the().invalidate_cursor(); + if (&window == WindowManager::the().hovered_window()) + Compositor::the().invalidate_cursor(); return make(); } diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 143b53793b..c1a15d32e3 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -127,8 +127,10 @@ void Compositor::compose() { auto& current_cursor = wm.active_cursor(); - if (m_current_cursor != ¤t_cursor) + if (m_current_cursor != ¤t_cursor) { change_cursor(¤t_cursor); + m_invalidated_cursor = m_invalidated_any = true; + } } if (!m_invalidated_any) { diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index dc7fb82819..5174b7d462 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -1043,6 +1043,52 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind clear_resize_candidate(); } +void WindowManager::reevaluate_hovered_window(Window* updated_window) +{ + if (m_dnd_client || m_resize_window || m_move_window || m_cursor_tracking_button || MenuManager::the().has_open_menu()) + return; + + auto cursor_location = Screen::the().cursor_location(); + auto* currently_hovered = hovered_window(); + if (updated_window) { + if (!(updated_window == currently_hovered || updated_window->frame().rect().contains(cursor_location) || (currently_hovered && currently_hovered->frame().rect().contains(cursor_location)))) + return; + } + + Window* hovered_window = nullptr; + if (auto* fullscreen_window = active_fullscreen_window()) { + if (fullscreen_window->hit_test(cursor_location)) + hovered_window = fullscreen_window; + } else { + for_each_visible_window_from_front_to_back([&](Window& window) { + if (!window.hit_test(cursor_location)) + return IterationDecision::Continue; + hovered_window = &window; + return IterationDecision::Break; + }); + } + + if (set_hovered_window(hovered_window)) { + if (currently_hovered && m_resize_candidate == currently_hovered) + clear_resize_candidate(); + + if (hovered_window) { + // Send a fake MouseMove event. This allows the new hovering window + // to determine which widget we're hovering, and also update the cursor + // accordingly. We do this because this re-evaluation of the currently + // hovered window wasn't triggered by a mouse move event, but rather + // e.g. a hit-test result change due to a transparent window repaint. + if (hovered_window->hit_test(cursor_location, false)) { + MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->rect().location()), 0, MouseButton::None, 0); + hovered_window->event(event); + } else if (!hovered_window->is_frameless()) { + MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->frame().rect().location()), 0, MouseButton::None, 0); + hovered_window->frame().on_mouse_event(event); + } + } + } +} + void WindowManager::clear_resize_candidate() { if (m_resize_candidate) @@ -1315,10 +1361,10 @@ void WindowManager::set_active_window(Window* window, bool make_input) Compositor::the().invalidate_occlusions(); } -void WindowManager::set_hovered_window(Window* window) +bool WindowManager::set_hovered_window(Window* window) { if (m_hovered_window == window) - return; + return false; if (m_hovered_window) Core::EventLoop::current().post_event(*m_hovered_window, make(Event::WindowLeft)); @@ -1327,6 +1373,7 @@ void WindowManager::set_hovered_window(Window* window) if (m_hovered_window) Core::EventLoop::current().post_event(*m_hovered_window, make(Event::WindowEntered)); + return true; } const ClientConnection* WindowManager::active_client() const diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index 52f2e530c6..2056c8714c 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -189,7 +189,7 @@ public: bool update_theme(String theme_path, String theme_name); - void set_hovered_window(Window*); + bool set_hovered_window(Window*); void deliver_mouse_event(Window& window, MouseEvent& event, bool process_double_click); void did_popup_a_menu(Badge); @@ -234,6 +234,9 @@ public: int compositor_icon_scale() const; void reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons = true); + void reevaluate_hovered_window(Window* = nullptr); + Window* hovered_window() const { return m_hovered_window.ptr(); } + private: NonnullRefPtr get_cursor(const String& name);