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:
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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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&);
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue