1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 06:04:57 +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:
Andreas Kling 2021-06-18 00:47:38 +02:00
parent 370d3749e5
commit 4133caba78
6 changed files with 91 additions and 45 deletions

View 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 };
};
}

View file

@ -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<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)
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)

View file

@ -6,6 +6,7 @@
#pragma once
#include "HitTestResult.h"
#include <AK/String.h>
#include <AK/WeakPtr.h>
#include <LibCore/Object.h>
@ -138,7 +139,7 @@ public:
{
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 y() const { return m_rect.y(); }

View file

@ -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<HitTestResult> 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)

View file

@ -6,6 +6,7 @@
#pragma once
#include "HitTestResult.h"
#include <AK/Forward.h>
#include <AK/NonnullOwnPtrVector.h>
#include <AK/RefPtr.h>
@ -78,7 +79,7 @@ public:
void theme_changed();
bool hit_test(const Gfx::IntPoint&) const;
Optional<HitTestResult> hit_test(Gfx::IntPoint const&) const;
void open_menubar_menu(Menu&);

View file

@ -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()) {