From 4133caba78ea363ec5e4042872746e28f5fa6f90 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 18 Jun 2021 00:47:38 +0200 Subject: [PATCH] WindowServer: Make hit test results richer Instead of just answering hit/no-hit when hit testing windows, we now return a HitTestResult object which tells you which window was hit, where it was hit, and whether you hit the frame or the content. --- .../Services/WindowServer/HitTestResult.h | 23 ++++++++++ Userland/Services/WindowServer/Window.cpp | 43 ++++++++++------- Userland/Services/WindowServer/Window.h | 3 +- .../Services/WindowServer/WindowFrame.cpp | 46 +++++++++++-------- Userland/Services/WindowServer/WindowFrame.h | 3 +- .../Services/WindowServer/WindowManager.cpp | 18 ++++---- 6 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 Userland/Services/WindowServer/HitTestResult.h diff --git a/Userland/Services/WindowServer/HitTestResult.h b/Userland/Services/WindowServer/HitTestResult.h new file mode 100644 index 0000000000..2e931eec5a --- /dev/null +++ b/Userland/Services/WindowServer/HitTestResult.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace WindowServer { + +class Window; + +struct HitTestResult { + WeakPtr window; + Gfx::IntPoint screen_position; + Gfx::IntPoint window_relative_position; + bool is_frame_hit { false }; +}; + +} diff --git a/Userland/Services/WindowServer/Window.cpp b/Userland/Services/WindowServer/Window.cpp index 0306b67139..0b89368725 100644 --- a/Userland/Services/WindowServer/Window.cpp +++ b/Userland/Services/WindowServer/Window.cpp @@ -953,25 +953,36 @@ bool Window::is_descendant_of(Window& window) const return false; } -bool Window::hit_test(const Gfx::IntPoint& point, bool include_frame) const +Optional Window::hit_test(Gfx::IntPoint const& position, bool include_frame) const { - if (!frame().rect().contains(point)) - return false; - if (!rect().contains(point)) { - if (include_frame) - return frame().hit_test(point); - return false; - } if (!m_hit_testing_enabled) - return false; + return {}; + if (!frame().rect().contains(position)) + return {}; + if (!rect().contains(position)) { + if (include_frame) + return frame().hit_test(position); + return {}; + } + bool hit = false; u8 threshold = alpha_hit_threshold() * 255; - if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel()) - return true; - auto relative_point = point.translated(-rect().location()) * m_backing_store->scale(); - u8 alpha = 0xff; - if (m_backing_store->rect().contains(relative_point)) - alpha = m_backing_store->get_pixel(relative_point).alpha(); - return alpha >= threshold; + if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel()) { + hit = true; + } else { + auto relative_point = position.translated(-rect().location()) * m_backing_store->scale(); + u8 alpha = 0xff; + if (m_backing_store->rect().contains(relative_point)) + alpha = m_backing_store->get_pixel(relative_point).alpha(); + hit = alpha >= threshold; + } + if (!hit) + return {}; + return HitTestResult { + .window = *this, + .screen_position = position, + .window_relative_position = position.translated(-rect().location()), + .is_frame_hit = false, + }; } void Window::set_menubar(Menubar* menubar) diff --git a/Userland/Services/WindowServer/Window.h b/Userland/Services/WindowServer/Window.h index c45b982054..bf1403ebac 100644 --- a/Userland/Services/WindowServer/Window.h +++ b/Userland/Services/WindowServer/Window.h @@ -6,6 +6,7 @@ #pragma once +#include "HitTestResult.h" #include #include #include @@ -138,7 +139,7 @@ public: { m_alpha_hit_threshold = threshold; } - bool hit_test(const Gfx::IntPoint&, bool include_frame = true) const; + Optional hit_test(const Gfx::IntPoint&, bool include_frame = true) const; int x() const { return m_rect.x(); } int y() const { return m_rect.y(); } diff --git a/Userland/Services/WindowServer/WindowFrame.cpp b/Userland/Services/WindowServer/WindowFrame.cpp index 5068e1e4a0..36b3e30f8b 100644 --- a/Userland/Services/WindowServer/WindowFrame.cpp +++ b/Userland/Services/WindowServer/WindowFrame.cpp @@ -591,50 +591,60 @@ void WindowFrame::layout_buttons() m_buttons[i].set_relative_rect(button_rects[i]); } -bool WindowFrame::hit_test(const Gfx::IntPoint& point) const +Optional WindowFrame::hit_test(Gfx::IntPoint const& position) const { if (m_window.is_frameless()) - return false; + return {}; auto frame_rect = rect(); - if (!frame_rect.contains(point)) - return false; + if (!frame_rect.contains(position)) + return {}; auto window_rect = m_window.rect(); - if (window_rect.contains(point)) - return false; + if (window_rect.contains(position)) + return {}; + + auto window_relative_position = position.translated(-render_rect().location()); + HitTestResult result { + .window = m_window, + .screen_position = position, + .window_relative_position = window_relative_position, + .is_frame_hit = true, + }; u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(window_state_for_theme()) * 255; if (alpha_threshold == 0) - return true; + return result; u8 alpha = 0xff; - auto relative_point = point.translated(-render_rect().location()); - if (point.y() < window_rect.y()) { + + if (position.y() < window_rect.y()) { if (m_top_bottom) { - auto scaled_relative_point = relative_point * m_top_bottom->scale(); + auto scaled_relative_point = window_relative_position * m_top_bottom->scale(); if (m_top_bottom->rect().contains(scaled_relative_point)) alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha(); } - } else if (point.y() > window_rect.bottom()) { + } else if (position.y() > window_rect.bottom()) { if (m_top_bottom) { - Gfx::IntPoint scaled_relative_point { relative_point.x() * m_top_bottom->scale(), m_bottom_y * m_top_bottom->scale() + point.y() - window_rect.bottom() - 1 }; + Gfx::IntPoint scaled_relative_point { window_relative_position.x() * m_top_bottom->scale(), m_bottom_y * m_top_bottom->scale() + position.y() - window_rect.bottom() - 1 }; if (m_top_bottom->rect().contains(scaled_relative_point)) alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha(); } - } else if (point.x() < window_rect.x()) { + } else if (position.x() < window_rect.x()) { if (m_left_right) { - Gfx::IntPoint scaled_relative_point { relative_point.x() * m_left_right->scale(), (relative_point.y() - m_bottom_y) * m_left_right->scale() }; + Gfx::IntPoint scaled_relative_point { window_relative_position.x() * m_left_right->scale(), (window_relative_position.y() - m_bottom_y) * m_left_right->scale() }; if (m_left_right->rect().contains(scaled_relative_point)) alpha = m_left_right->get_pixel(scaled_relative_point).alpha(); } - } else if (point.x() > window_rect.right()) { + } else if (position.x() > window_rect.right()) { if (m_left_right) { - Gfx::IntPoint scaled_relative_point { m_right_x * m_left_right->scale() + point.x() - window_rect.right() - 1, (relative_point.y() - m_bottom_y) * m_left_right->scale() }; + Gfx::IntPoint scaled_relative_point { m_right_x * m_left_right->scale() + position.x() - window_rect.right() - 1, (window_relative_position.y() - m_bottom_y) * m_left_right->scale() }; if (m_left_right->rect().contains(scaled_relative_point)) alpha = m_left_right->get_pixel(scaled_relative_point).alpha(); } } else { - return false; + return {}; } - return alpha >= alpha_threshold; + if (alpha >= alpha_threshold) + return result; + return {}; } void WindowFrame::on_mouse_event(const MouseEvent& event) diff --git a/Userland/Services/WindowServer/WindowFrame.h b/Userland/Services/WindowServer/WindowFrame.h index fc237fda91..d32b54f0df 100644 --- a/Userland/Services/WindowServer/WindowFrame.h +++ b/Userland/Services/WindowServer/WindowFrame.h @@ -6,6 +6,7 @@ #pragma once +#include "HitTestResult.h" #include #include #include @@ -78,7 +79,7 @@ public: void theme_changed(); - bool hit_test(const Gfx::IntPoint&) const; + Optional hit_test(Gfx::IntPoint const&) const; void open_menubar_menu(Menu&); diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 19c382d55b..3a72630e0a 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -767,7 +767,7 @@ bool WindowManager::process_ongoing_drag(MouseEvent& event, Window*& hovered_win hovered_window = nullptr; m_window_stack.for_each_visible_window_from_front_to_back([&](auto& window) { - if (window.hit_test(event.position())) { + if (window.hit_test(event.position()).has_value()) { hovered_window = &window; return IterationDecision::Break; } @@ -996,7 +996,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind } m_window_stack.for_each_visible_window_from_front_to_back([&](auto& window) { - if (window.hit_test(event.position())) { + if (window.hit_test(event.position()).has_value()) { hovered_window = &window; return IterationDecision::Break; } @@ -1022,7 +1022,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind } } - VERIFY(window.hit_test(event.position())); + VERIFY(window.hit_test(event.position()).has_value()); if (event.type() == Event::MouseDown) { // We're clicking on something that's blocked by a modal window. // Flash the modal window to let the user know about it. @@ -1035,11 +1035,11 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind set_active_window(&window); } - if (window.frame().hit_test(event.position())) { + if (window.frame().hit_test(event.position()).has_value()) { // We are hitting the frame, pass the event along to WindowFrame. window.frame().on_mouse_event(event.translated(-window.frame().rect().location())); event_window_with_frame = &window; - } else if (window.hit_test(event.position(), false)) { + } else if (window.hit_test(event.position(), false).has_value()) { // We are hitting the window content hovered_window = &window; if (!window.global_cursor_tracking() && !window.blocking_modal_window()) { @@ -1057,7 +1057,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind process_mouse_event_for_window(*fullscreen_window); } else { m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) { - if (!window.hit_test(event.position())) + if (!window.hit_test(event.position()).has_value()) return IterationDecision::Continue; process_mouse_event_for_window(window); return IterationDecision::Break; @@ -1098,11 +1098,11 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window) Window* hovered_window = nullptr; if (auto* fullscreen_window = active_fullscreen_window()) { - if (fullscreen_window->hit_test(cursor_location)) + if (fullscreen_window->hit_test(cursor_location).has_value()) hovered_window = fullscreen_window; } else { m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) { - if (!window.hit_test(cursor_location)) + if (!window.hit_test(cursor_location).has_value()) return IterationDecision::Continue; hovered_window = &window; return IterationDecision::Break; @@ -1119,7 +1119,7 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window) // 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)) { + if (hovered_window->hit_test(cursor_location, false).has_value()) { MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->rect().location()), 0, MouseButton::None, 0); hovered_window->dispatch_event(event); } else if (!hovered_window->is_frameless()) {