mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:18:11 +00:00
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.
This commit is contained in:
parent
370d3749e5
commit
4133caba78
6 changed files with 91 additions and 45 deletions
23
Userland/Services/WindowServer/HitTestResult.h
Normal file
23
Userland/Services/WindowServer/HitTestResult.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/WeakPtr.h>
|
||||||
|
#include <LibGfx/Point.h>
|
||||||
|
|
||||||
|
namespace WindowServer {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
|
||||||
|
struct HitTestResult {
|
||||||
|
WeakPtr<Window> window;
|
||||||
|
Gfx::IntPoint screen_position;
|
||||||
|
Gfx::IntPoint window_relative_position;
|
||||||
|
bool is_frame_hit { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -953,25 +953,36 @@ bool Window::is_descendant_of(Window& window) const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Window::hit_test(const Gfx::IntPoint& point, bool include_frame) const
|
Optional<HitTestResult> 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)
|
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;
|
u8 threshold = alpha_hit_threshold() * 255;
|
||||||
if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel())
|
if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel()) {
|
||||||
return true;
|
hit = true;
|
||||||
auto relative_point = point.translated(-rect().location()) * m_backing_store->scale();
|
} else {
|
||||||
u8 alpha = 0xff;
|
auto relative_point = position.translated(-rect().location()) * m_backing_store->scale();
|
||||||
if (m_backing_store->rect().contains(relative_point))
|
u8 alpha = 0xff;
|
||||||
alpha = m_backing_store->get_pixel(relative_point).alpha();
|
if (m_backing_store->rect().contains(relative_point))
|
||||||
return alpha >= threshold;
|
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)
|
void Window::set_menubar(Menubar* menubar)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "HitTestResult.h"
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/WeakPtr.h>
|
#include <AK/WeakPtr.h>
|
||||||
#include <LibCore/Object.h>
|
#include <LibCore/Object.h>
|
||||||
|
@ -138,7 +139,7 @@ public:
|
||||||
{
|
{
|
||||||
m_alpha_hit_threshold = threshold;
|
m_alpha_hit_threshold = threshold;
|
||||||
}
|
}
|
||||||
bool hit_test(const Gfx::IntPoint&, bool include_frame = true) const;
|
Optional<HitTestResult> hit_test(const Gfx::IntPoint&, bool include_frame = true) const;
|
||||||
|
|
||||||
int x() const { return m_rect.x(); }
|
int x() const { return m_rect.x(); }
|
||||||
int y() const { return m_rect.y(); }
|
int y() const { return m_rect.y(); }
|
||||||
|
|
|
@ -591,50 +591,60 @@ void WindowFrame::layout_buttons()
|
||||||
m_buttons[i].set_relative_rect(button_rects[i]);
|
m_buttons[i].set_relative_rect(button_rects[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowFrame::hit_test(const Gfx::IntPoint& point) const
|
Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position) const
|
||||||
{
|
{
|
||||||
if (m_window.is_frameless())
|
if (m_window.is_frameless())
|
||||||
return false;
|
return {};
|
||||||
auto frame_rect = rect();
|
auto frame_rect = rect();
|
||||||
if (!frame_rect.contains(point))
|
if (!frame_rect.contains(position))
|
||||||
return false;
|
return {};
|
||||||
auto window_rect = m_window.rect();
|
auto window_rect = m_window.rect();
|
||||||
if (window_rect.contains(point))
|
if (window_rect.contains(position))
|
||||||
return false;
|
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;
|
u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(window_state_for_theme()) * 255;
|
||||||
if (alpha_threshold == 0)
|
if (alpha_threshold == 0)
|
||||||
return true;
|
return result;
|
||||||
u8 alpha = 0xff;
|
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) {
|
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))
|
if (m_top_bottom->rect().contains(scaled_relative_point))
|
||||||
alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha();
|
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) {
|
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))
|
if (m_top_bottom->rect().contains(scaled_relative_point))
|
||||||
alpha = m_top_bottom->get_pixel(scaled_relative_point).alpha();
|
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) {
|
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))
|
if (m_left_right->rect().contains(scaled_relative_point))
|
||||||
alpha = m_left_right->get_pixel(scaled_relative_point).alpha();
|
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) {
|
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))
|
if (m_left_right->rect().contains(scaled_relative_point))
|
||||||
alpha = m_left_right->get_pixel(scaled_relative_point).alpha();
|
alpha = m_left_right->get_pixel(scaled_relative_point).alpha();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return {};
|
||||||
}
|
}
|
||||||
return alpha >= alpha_threshold;
|
if (alpha >= alpha_threshold)
|
||||||
|
return result;
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowFrame::on_mouse_event(const MouseEvent& event)
|
void WindowFrame::on_mouse_event(const MouseEvent& event)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "HitTestResult.h"
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
#include <AK/NonnullOwnPtrVector.h>
|
#include <AK/NonnullOwnPtrVector.h>
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
|
@ -78,7 +79,7 @@ public:
|
||||||
|
|
||||||
void theme_changed();
|
void theme_changed();
|
||||||
|
|
||||||
bool hit_test(const Gfx::IntPoint&) const;
|
Optional<HitTestResult> hit_test(Gfx::IntPoint const&) const;
|
||||||
|
|
||||||
void open_menubar_menu(Menu&);
|
void open_menubar_menu(Menu&);
|
||||||
|
|
||||||
|
|
|
@ -767,7 +767,7 @@ bool WindowManager::process_ongoing_drag(MouseEvent& event, Window*& hovered_win
|
||||||
|
|
||||||
hovered_window = nullptr;
|
hovered_window = nullptr;
|
||||||
m_window_stack.for_each_visible_window_from_front_to_back([&](auto& window) {
|
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;
|
hovered_window = &window;
|
||||||
return IterationDecision::Break;
|
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) {
|
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;
|
hovered_window = &window;
|
||||||
return IterationDecision::Break;
|
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) {
|
if (event.type() == Event::MouseDown) {
|
||||||
// We're clicking on something that's blocked by a modal window.
|
// We're clicking on something that's blocked by a modal window.
|
||||||
// Flash the modal window to let the user know about it.
|
// 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);
|
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.
|
// We are hitting the frame, pass the event along to WindowFrame.
|
||||||
window.frame().on_mouse_event(event.translated(-window.frame().rect().location()));
|
window.frame().on_mouse_event(event.translated(-window.frame().rect().location()));
|
||||||
event_window_with_frame = &window;
|
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
|
// We are hitting the window content
|
||||||
hovered_window = &window;
|
hovered_window = &window;
|
||||||
if (!window.global_cursor_tracking() && !window.blocking_modal_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);
|
process_mouse_event_for_window(*fullscreen_window);
|
||||||
} else {
|
} else {
|
||||||
m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) {
|
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;
|
return IterationDecision::Continue;
|
||||||
process_mouse_event_for_window(window);
|
process_mouse_event_for_window(window);
|
||||||
return IterationDecision::Break;
|
return IterationDecision::Break;
|
||||||
|
@ -1098,11 +1098,11 @@ void WindowManager::reevaluate_hovered_window(Window* updated_window)
|
||||||
|
|
||||||
Window* hovered_window = nullptr;
|
Window* hovered_window = nullptr;
|
||||||
if (auto* fullscreen_window = active_fullscreen_window()) {
|
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;
|
hovered_window = fullscreen_window;
|
||||||
} else {
|
} else {
|
||||||
m_window_stack.for_each_visible_window_from_front_to_back([&](Window& window) {
|
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;
|
return IterationDecision::Continue;
|
||||||
hovered_window = &window;
|
hovered_window = &window;
|
||||||
return IterationDecision::Break;
|
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
|
// accordingly. We do this because this re-evaluation of the currently
|
||||||
// hovered window wasn't triggered by a mouse move event, but rather
|
// 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.
|
// 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);
|
MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->rect().location()), 0, MouseButton::None, 0);
|
||||||
hovered_window->dispatch_event(event);
|
hovered_window->dispatch_event(event);
|
||||||
} else if (!hovered_window->is_frameless()) {
|
} else if (!hovered_window->is_frameless()) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue