1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 11:47:35 +00:00

WindowServer: Add support for alpha channel based hit testing

This enables implementing non-rectangular window shapes, including
non-rectangular window frames.
This commit is contained in:
Tom 2021-02-14 16:42:37 -07:00 committed by Andreas Kling
parent b3f0a5c917
commit d590e0c946
12 changed files with 107 additions and 12 deletions

View file

@ -147,6 +147,7 @@ void Window::show()
m_frameless,
m_accessory,
m_opacity_when_windowless,
m_alpha_hit_threshold,
m_base_size,
m_size_increment,
m_resize_aspect_ratio,
@ -673,6 +674,20 @@ void Window::set_opacity(float opacity)
WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowOpacity>(m_window_id, opacity);
}
void Window::set_alpha_hit_threshold(float threshold)
{
if (threshold < 0.0f)
threshold = 0.0f;
else if (threshold > 1.0f)
threshold = 1.0f;
if (m_alpha_hit_threshold == threshold)
return;
m_alpha_hit_threshold = threshold;
if (!is_visible())
return;
WindowServerConnection::the().send_sync<Messages::WindowServer::SetWindowAlphaHitThreshold>(m_window_id, threshold);
}
void Window::set_hovered_widget(Widget* widget)
{
if (widget == m_hovered_widget)

View file

@ -72,6 +72,9 @@ public:
void set_opacity(float);
float opacity() const { return m_opacity_when_windowless; }
void set_alpha_hit_threshold(float);
float alpha_hit_threshold() const { return m_alpha_hit_threshold; }
WindowType window_type() const { return m_window_type; }
void set_window_type(WindowType);
@ -238,6 +241,7 @@ private:
RefPtr<Gfx::Bitmap> m_custom_cursor;
int m_window_id { 0 };
float m_opacity_when_windowless { 1.0f };
float m_alpha_hit_threshold { 0.0f };
RefPtr<Widget> m_main_widget;
WeakPtr<Widget> m_focused_widget;
WeakPtr<Widget> m_global_cursor_tracking_widget;

View file

@ -52,6 +52,7 @@ public:
{
return compute_frame_colors(state, palette).uses_alpha();
}
virtual float frame_alpha_hit_threshold(WindowState) const override { return 1.0f; }
private:
struct FrameColors {

View file

@ -63,6 +63,7 @@ public:
virtual Vector<IntRect> layout_buttons(WindowType, const IntRect& window_rect, const Palette&, size_t buttons) const = 0;
virtual bool is_simple_rect_frame() const = 0;
virtual bool frame_uses_alpha(WindowState, const Palette&) const = 0;
virtual float frame_alpha_hit_threshold(WindowState) const = 0;
protected:
WindowTheme() { }

View file

@ -463,6 +463,7 @@ OwnPtr<Messages::WindowServer::CreateWindowResponse> ClientConnection::handle(co
window->recalculate_rect();
}
window->set_opacity(message.opacity());
window->set_alpha_hit_threshold(message.alpha_hit_threshold());
window->set_size_increment(message.size_increment());
window->set_base_size(message.base_size());
window->set_resize_aspect_ratio(message.resize_aspect_ratio());
@ -636,6 +637,17 @@ OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> ClientConnectio
return make<Messages::WindowServer::SetWindowHasAlphaChannelResponse>();
}
OwnPtr<Messages::WindowServer::SetWindowAlphaHitThresholdResponse> ClientConnection::handle(const Messages::WindowServer::SetWindowAlphaHitThreshold& message)
{
auto it = m_windows.find(message.window_id());
if (it == m_windows.end()) {
did_misbehave("SetWindowAlphaHitThreshold: Bad window ID");
return {};
}
it->value->set_alpha_hit_threshold(message.threshold());
return make<Messages::WindowServer::SetWindowAlphaHitThresholdResponse>();
}
void ClientConnection::handle(const Messages::WindowServer::WM_SetActiveWindow& message)
{
auto* client = ClientConnection::from_client_id(message.client_id());

View file

@ -120,6 +120,7 @@ private:
virtual void handle(const Messages::WindowServer::WM_StartWindowResize&) override;
virtual void handle(const Messages::WindowServer::WM_PopupWindowMenu&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowHasAlphaChannelResponse> handle(const Messages::WindowServer::SetWindowHasAlphaChannel&) override;
virtual OwnPtr<Messages::WindowServer::SetWindowAlphaHitThresholdResponse> handle(const Messages::WindowServer::SetWindowAlphaHitThreshold&) override;
virtual OwnPtr<Messages::WindowServer::MoveWindowToFrontResponse> handle(const Messages::WindowServer::MoveWindowToFront&) override;
virtual OwnPtr<Messages::WindowServer::SetFullscreenResponse> handle(const Messages::WindowServer::SetFullscreen&) override;
virtual void handle(const Messages::WindowServer::AsyncSetWallpaper&) override;

View file

@ -873,4 +873,20 @@ bool Window::is_descendant_of(Window& window) const
return false;
}
bool Window::hit_test(const Gfx::IntPoint& point, 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;
}
u8 threshold = alpha_hit_threshold() * 255;
if (threshold == 0 || !m_backing_store || !m_backing_store->has_alpha_channel())
return true;
auto color = m_backing_store->get_pixel(point.translated(-rect().location()));
return color.alpha() >= threshold;
}
}

View file

@ -145,6 +145,13 @@ public:
float opacity() const { return m_opacity; }
void set_opacity(float);
float alpha_hit_threshold() const { return m_alpha_hit_threshold; }
void set_alpha_hit_threshold(float threshold)
{
m_alpha_hit_threshold = threshold;
}
bool hit_test(const Gfx::IntPoint&, bool include_frame = true) const;
int x() const { return m_rect.x(); }
int y() const { return m_rect.y(); }
int width() const { return m_rect.width(); }
@ -365,6 +372,7 @@ private:
int m_window_id { -1 };
i32 m_client_id { -1 };
float m_opacity { 1 };
float m_alpha_hit_threshold { 0.0f };
Gfx::IntSize m_size_increment;
Gfx::IntSize m_base_size;
NonnullRefPtr<Gfx::Bitmap> m_icon;

View file

@ -535,6 +535,40 @@ void WindowFrame::layout_buttons()
m_buttons[i].set_relative_rect(button_rects[i]);
}
bool WindowFrame::hit_test(const Gfx::IntPoint& point) const
{
if (m_window.is_frameless())
return false;
auto frame_rect = rect();
if (!frame_rect.contains(point))
return false;
auto window_rect = m_window.rect();
if (window_rect.contains(point))
return false;
u8 alpha_threshold = Gfx::WindowTheme::current().frame_alpha_hit_threshold(window_state_for_theme()) * 255;
if (alpha_threshold == 0)
return true;
u8 alpha = 0xff;
auto relative_point = point.translated(-render_rect().location());
if (point.y() < window_rect.y()) {
if (m_top_bottom)
alpha = m_top_bottom->get_pixel(relative_point).alpha();
} else if (point.y() > window_rect.bottom()) {
if (m_top_bottom)
alpha = m_top_bottom->get_pixel(relative_point.x(), m_bottom_y + point.y() - window_rect.bottom() - 1).alpha();
} else if (point.x() < window_rect.x()) {
if (m_left_right)
alpha = m_left_right->get_pixel(relative_point.x(), relative_point.y() - m_bottom_y).alpha();
} else if (point.x() > window_rect.right()) {
if (m_left_right)
alpha = m_left_right->get_pixel(m_right_x + point.x() - window_rect.right() - 1, relative_point.y() - m_bottom_y).alpha();
} else {
return false;
}
return alpha >= alpha_threshold;
}
void WindowFrame::on_mouse_event(const MouseEvent& event)
{
ASSERT(!m_window.is_fullscreen());

View file

@ -90,6 +90,8 @@ public:
void theme_changed();
bool hit_test(const Gfx::IntPoint&) const;
private:
void paint_simple_rect_shadow(Gfx::Painter&, const Gfx::IntRect&, const Gfx::Bitmap&) const;
void paint_notification_frame(Gfx::Painter&);

View file

@ -726,7 +726,7 @@ bool WindowManager::process_ongoing_drag(MouseEvent& event, Window*& hovered_win
hovered_window = nullptr;
for_each_visible_window_from_front_to_back([&](auto& window) {
if (window.frame().rect().contains(event.position())) {
if (window.hit_test(event.position())) {
hovered_window = &window;
return IterationDecision::Break;
}
@ -940,7 +940,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
}
for_each_visible_window_from_front_to_back([&](auto& window) {
if (window.frame().rect().contains(event.position())) {
if (window.hit_test(event.position())) {
hovered_window = &window;
return IterationDecision::Break;
}
@ -977,7 +977,7 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
return;
}
ASSERT(window.frame().rect().contains(event.position()));
ASSERT(window.hit_test(event.position()));
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.
@ -990,8 +990,12 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
set_active_window(&window);
}
// Well okay, let's see if we're hitting the frame or the window inside the frame.
if (window.rect().contains(event.position())) {
if (window.frame().hit_test(event.position())) {
// 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)) {
// We are hitting the window content
hovered_window = &window;
if (!window.global_cursor_tracking() && !window.blocking_modal_window()) {
auto translated_event = event.translated(-window.position());
@ -1001,20 +1005,14 @@ void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_wind
m_active_input_tracking_window = window;
}
}
return;
}
// 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;
};
if (auto* fullscreen_window = active_fullscreen_window()) {
process_mouse_event_for_window(*fullscreen_window);
} else {
for_each_visible_window_from_front_to_back([&](Window& window) {
auto window_frame_rect = window.frame().rect();
if (!window_frame_rect.contains(event.position()))
if (!window.hit_test(event.position()))
return IterationDecision::Continue;
process_mouse_event_for_window(window);
return IterationDecision::Break;

View file

@ -41,6 +41,7 @@ endpoint WindowServer = 2
bool frameless,
bool accessory,
float opacity,
float alpha_hit_threshold,
Gfx::IntSize base_size,
Gfx::IntSize size_increment,
Optional<Gfx::IntSize> resize_aspect_ratio,
@ -70,6 +71,8 @@ endpoint WindowServer = 2
SetGlobalCursorTracking(i32 window_id, bool enabled) => ()
SetWindowOpacity(i32 window_id, float opacity) => ()
SetWindowAlphaHitThreshold(i32 window_id, float threshold) => ()
SetWindowBackingStore(i32 window_id, i32 bpp, i32 pitch, IPC::File anon_file, i32 serial, bool has_alpha_channel, Gfx::IntSize size, bool flush_immediately) => ()
WM_SetActiveWindow(i32 client_id, i32 window_id) =|