From 62b3b77ea8347385338db253437f29cd46eeeceb Mon Sep 17 00:00:00 2001 From: ci_lynx Date: Mon, 24 Aug 2026 16:11:15 +0800 Subject: [PATCH] [Feature][BugFix] Support Windows cover view overlays and event-through - Add a reusable Windows overlay window and synchronize cover view bounds with platform rendering. - Route pointer coordinates through the host window and honor event-through for hover and pointer hit testing. - Avoid the Windows SDK ReportEvent macro collision in performance event reporting. TEST: Not run (commit consolidation only). SkipChecks: cpplint --- clay/lynx_adaptor/perf_controller_clay.cc | 2 +- clay/shell/platform/windows/BUILD.gn | 2 + clay/shell/platform/windows/flutter_window.cc | 22 +++- clay/shell/platform/windows/flutter_window.h | 6 + .../windows/flutter_windows_engine.cc | 27 +++-- .../platform/windows/flutter_windows_engine.h | 2 + .../platform/windows/flutter_windows_view.cc | 14 +++ .../platform/windows/flutter_windows_view.h | 4 + .../windows/overlay_platform_plugin_win.cc | 104 ++++++++++++++++++ .../windows/overlay_platform_plugin_win.h | 71 ++++++++++++ .../windows/overlay_view_controller.cc | 15 ++- .../windows/overlay_view_controller.h | 3 + .../windows/overlay_view_manager_service.cc | 3 + .../windows/platform_overlay_service_win.cc | 8 +- clay/shell/platform/windows/window.cc | 9 ++ clay/shell/platform/windows/window.h | 3 + clay/ui/component/base_view.cc | 35 +++--- clay/ui/component/base_view.h | 5 +- clay/ui/component/build.gni | 7 ++ clay/ui/component/cover_view.cc | 78 ++++++++++++- clay/ui/component/cover_view.h | 19 ++++ .../component/cover_view_platform_delegate.cc | 68 ++++++++++++ .../component/cover_view_platform_delegate.h | 44 ++++++++ clay/ui/component/text/text_view.cc | 3 +- clay/ui/platform/build.gni | 1 + clay/ui/platform/overlay_service.h | 66 +++++++++++ core/public/perf_controller_proxy.h | 17 ++- core/shell/perf_controller_proxy_impl.cc | 3 +- core/shell/perf_controller_proxy_impl.h | 2 +- 29 files changed, 601 insertions(+), 42 deletions(-) create mode 100644 clay/shell/platform/windows/overlay_platform_plugin_win.cc create mode 100644 clay/shell/platform/windows/overlay_platform_plugin_win.h create mode 100644 clay/ui/component/cover_view_platform_delegate.cc create mode 100644 clay/ui/component/cover_view_platform_delegate.h create mode 100644 clay/ui/platform/overlay_service.h diff --git a/clay/lynx_adaptor/perf_controller_clay.cc b/clay/lynx_adaptor/perf_controller_clay.cc index a7507da90e..9ad1242622 100644 --- a/clay/lynx_adaptor/perf_controller_clay.cc +++ b/clay/lynx_adaptor/perf_controller_clay.cc @@ -282,7 +282,7 @@ void PerfControllerClay::EndFluencyMonitor(int id) { return; } - shell::ReportEvent event; + shell::PerfReportEvent event; event.event_name = std::string(kLynxFluencyEvent); // string props diff --git a/clay/shell/platform/windows/BUILD.gn b/clay/shell/platform/windows/BUILD.gn index 5dfc7720ba..472ba5254e 100644 --- a/clay/shell/platform/windows/BUILD.gn +++ b/clay/shell/platform/windows/BUILD.gn @@ -114,6 +114,8 @@ source_set("flutter_windows_source") { "keyboard_manager.h", "keyboard_utils.cc", "keyboard_utils.h", + "overlay_platform_plugin_win.cc", + "overlay_platform_plugin_win.h", "overlay_view_controller.cc", "overlay_view_controller.h", "overlay_view_manager_service.cc", diff --git a/clay/shell/platform/windows/flutter_window.cc b/clay/shell/platform/windows/flutter_window.cc index 98c0c047db..79af9035e5 100644 --- a/clay/shell/platform/windows/flutter_window.cc +++ b/clay/shell/platform/windows/flutter_window.cc @@ -185,7 +185,7 @@ static HCURSOR GetCursorByType(clay::CursorTypes cursor_type) { FlutterWindow::FlutterWindow(HWND parent_hwnd, int x, int y, int width, int height) - : binding_handler_delegate_(nullptr) { + : parent_hwnd_(parent_hwnd), binding_handler_delegate_(nullptr) { Window::InitializeChild("FLUTTERVIEW", parent_hwnd, x, y, width, height); auto cursor = ::LoadCursor(nullptr, IDC_ARROW); SetClassLongPtr(GetWindowHandle(), GCLP_HCURSOR, @@ -273,6 +273,7 @@ void FlutterWindow::OnPaint() { void FlutterWindow::OnPointerMove(double x, double y, ClayPointerDeviceKind device_kind, int32_t device_id, int modifiers_state) { + ConvertPointToParentWindow(&x, &y); binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id, modifiers_state); } @@ -280,6 +281,7 @@ void FlutterWindow::OnPointerMove(double x, double y, void FlutterWindow::OnPointerDown(double x, double y, ClayPointerDeviceKind device_kind, int32_t device_id, UINT button) { + ConvertPointToParentWindow(&x, &y); uint64_t flutter_button = ConvertWinButtonToFlutterButton(button); if (flutter_button != 0) { binding_handler_delegate_->OnPointerDown( @@ -291,6 +293,7 @@ void FlutterWindow::OnPointerDown(double x, double y, void FlutterWindow::OnPointerUp(double x, double y, ClayPointerDeviceKind device_kind, int32_t device_id, UINT button) { + ConvertPointToParentWindow(&x, &y); uint64_t flutter_button = ConvertWinButtonToFlutterButton(button); if (flutter_button != 0) { binding_handler_delegate_->OnPointerUp( @@ -302,6 +305,7 @@ void FlutterWindow::OnPointerUp(double x, double y, void FlutterWindow::OnPointerLeave(double x, double y, ClayPointerDeviceKind device_kind, int32_t device_id) { + ConvertPointToParentWindow(&x, &y); binding_handler_delegate_->OnPointerLeave(x, y, device_kind, device_id); } @@ -344,7 +348,8 @@ void FlutterWindow::OnScroll(double delta_x, double delta_y, POINT point; GetCursorPos(&point); - ScreenToClient(GetWindowHandle(), &point); + ScreenToClient(parent_hwnd_ ? parent_hwnd_ : Window::GetWindowHandle(), + &point); binding_handler_delegate_->OnScroll(point.x, point.y, delta_x, delta_y, GetScrollOffsetMultiplier(), device_kind, device_id); @@ -384,10 +389,21 @@ bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation, PointerLocation FlutterWindow::GetPrimaryPointerLocation() { POINT point; GetCursorPos(&point); - ScreenToClient(GetWindowHandle(), &point); + ScreenToClient(parent_hwnd_ ? parent_hwnd_ : Window::GetWindowHandle(), + &point); return {(size_t)point.x, (size_t)point.y}; } +void FlutterWindow::ConvertPointToParentWindow(double* x, double* y) { + if (!parent_hwnd_) { + return; + } + POINT point = {static_cast(*x), static_cast(*y)}; + MapWindowPoints(Window::GetWindowHandle(), parent_hwnd_, &point, 1); + *x = point.x; + *y = point.y; +} + void FlutterWindow::OnThemeChange() { binding_handler_delegate_->UpdateHighContrastEnabled( GetHighContrastEnabled()); diff --git a/clay/shell/platform/windows/flutter_window.h b/clay/shell/platform/windows/flutter_window.h index bc31ef8893..225f05f58b 100644 --- a/clay/shell/platform/windows/flutter_window.h +++ b/clay/shell/platform/windows/flutter_window.h @@ -131,6 +131,12 @@ class FlutterWindow : public Window, public WindowBindingHandler { bool NeedsVSync() override; private: + // Clay pointer events use the implicit view's physical coordinates, while + // Win32 child-window messages use coordinates local to the child. + void ConvertPointToParentWindow(double* x, double* y); + + HWND parent_hwnd_ = nullptr; + // A pointer to a FlutterWindowsView that can be used to update engine // windowing and input state. WindowBindingHandlerDelegate* binding_handler_delegate_; diff --git a/clay/shell/platform/windows/flutter_windows_engine.cc b/clay/shell/platform/windows/flutter_windows_engine.cc index fe1f4e6cf9..f1d1828c01 100644 --- a/clay/shell/platform/windows/flutter_windows_engine.cc +++ b/clay/shell/platform/windows/flutter_windows_engine.cc @@ -27,6 +27,7 @@ #include "clay/shell/common/switches.h" #include "clay/shell/platform/common/path_utils.h" #include "clay/shell/platform/windows/flutter_windows_view.h" +#include "clay/shell/platform/windows/overlay_platform_plugin_win.h" #include "clay/shell/platform/windows/overlay_view_manager_service.h" #include "clay/shell/platform/windows/overlay_windows_view.h" #include "clay/shell/platform/windows/task_runner.h" @@ -254,6 +255,21 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { FML_LOG(ERROR) << "Failed to start Flutter engine"; return false; } + + service_manager_ = engine_->GetServiceManager(); + if (!service_manager_) { + FML_LOG(ERROR) << "Failed to get clay service manager"; + return false; + } + // CoverView resolves OverlayService while the UI tree is being created, so + // register the Windows implementation before launching the shell. + overlay_view_manager_service_ = + std::make_shared(this); + service_manager_->RegisterService( + overlay_view_manager_service_); + overlay_platform_service_ = std::make_shared(); + service_manager_->RegisterService(overlay_platform_service_); + // Step 1: Launch the shell. if (!engine_->LaunchShell()) { FML_LOG(ERROR) << "Could not launch the engine using supplied " @@ -266,17 +282,6 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { FML_LOG(ERROR) << "Could not create platform view components."; return false; } - service_manager_ = engine_->GetServiceManager(); - if (!service_manager_) { - FML_LOG(ERROR) << "Failed to get clay service manager"; - return false; - } - - overlay_view_manager_service_ = - std::make_shared(this); - // |view_| can be nullptr in headless mode; avoid dereferencing it here. - service_manager_->RegisterService( - overlay_view_manager_service_); // Configure device frame rate displayed via devtools. std::vector> displays; diff --git a/clay/shell/platform/windows/flutter_windows_engine.h b/clay/shell/platform/windows/flutter_windows_engine.h index 50cd744846..b59f4bbdae 100644 --- a/clay/shell/platform/windows/flutter_windows_engine.h +++ b/clay/shell/platform/windows/flutter_windows_engine.h @@ -40,6 +40,7 @@ namespace clay { class FlutterWindowsView; class OverlayViewManagerService; +class OverlayService; // A unique identifier for a view. using FlutterViewId = int64_t; @@ -361,6 +362,7 @@ class FlutterWindowsEngine : public PlatformViewEmbedderDelegate, std::shared_ptr service_manager_; std::shared_ptr overlay_view_manager_service_; + std::shared_ptr overlay_platform_service_; }; } // namespace clay diff --git a/clay/shell/platform/windows/flutter_windows_view.cc b/clay/shell/platform/windows/flutter_windows_view.cc index 625feee0eb..55c169feb0 100644 --- a/clay/shell/platform/windows/flutter_windows_view.cc +++ b/clay/shell/platform/windows/flutter_windows_view.cc @@ -181,6 +181,20 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { return kWindowFrameBufferID; } +void FlutterWindowsView::PrepareSurfaceSize(size_t width, size_t height) { + std::unique_lock lock(resize_mutex_); + if (!surface_ || !surface_->IsValid()) { + return; + } + if (SurfaceWillUpdate(surface_->width(), surface_->height(), width, height) || + SurfaceWillUpdate(resize_target_width_, resize_target_height_, width, + height)) { + resize_status_ = ResizeState::kResizeStarted; + resize_target_width_ = width; + resize_target_height_ = height; + } +} + void FlutterWindowsView::SetDamageRegion(const clay::Rect& region) { if (!surface_) { return; diff --git a/clay/shell/platform/windows/flutter_windows_view.h b/clay/shell/platform/windows/flutter_windows_view.h index 0e55caf643..0b2f701df9 100644 --- a/clay/shell/platform/windows/flutter_windows_view.h +++ b/clay/shell/platform/windows/flutter_windows_view.h @@ -91,6 +91,10 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // Returns the frame buffer id for the engine to render to. uint32_t GetFrameBufferId(size_t width, size_t height); + // Prepares a surface resize for the next frame without blocking the caller. + // The actual resize is performed by GetFrameBufferId on the raster thread. + void PrepareSurfaceSize(size_t width, size_t height); + void SetDamageRegion(const clay::Rect& region); void SetPresentDamageRegion(const clay::Rect& region); diff --git a/clay/shell/platform/windows/overlay_platform_plugin_win.cc b/clay/shell/platform/windows/overlay_platform_plugin_win.cc new file mode 100644 index 0000000000..8215683faf --- /dev/null +++ b/clay/shell/platform/windows/overlay_platform_plugin_win.cc @@ -0,0 +1,104 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "clay/shell/platform/windows/overlay_platform_plugin_win.h" + +#include + +#include + +#include "clay/shell/platform/windows/flutter_windows_engine.h" + +namespace clay { + +OverlayPlatformPluginWin::OverlayPlatformPluginWin(FlutterWindowsEngine* engine, + OverlayViewManager* manager) + : engine_(engine), manager_(manager) {} + +void OverlayPlatformPluginWin::ChangeVisibility(bool visible) { + if (!view_) { + return; + } + HWND window = view_->GetWindowHandle(); + if (visible) { + ShowWindow(window, SW_SHOW); + } else { + ShowWindow(window, SW_HIDE); + } +} + +void OverlayPlatformPluginWin::SetEventThrough(bool event_through) { + event_through_ = event_through; + if (view_) { + view_->SetEventThrough(event_through); + } +} + +void OverlayPlatformPluginWin::SetSize(int width, int height) { + if (width <= 0 || height <= 0) { + return; + } + preferred_width_ = width; + preferred_height_ = height; + EnsureView(); +} + +void OverlayPlatformPluginWin::OnDetachFromTree() { ChangeVisibility(false); } + +void OverlayPlatformPluginWin::OnViewDestroy() { + if (manager_ && node_id_ != -1) { + manager_->RemoveView(node_id_); + } + view_.reset(); + node_id_ = -1; +} + +void OverlayPlatformPluginWin::InitPlatformOverlay( + std::shared_ptr>> overlay_listener, + int id, std::string tag, ExternalViewPlugin* recording_plugin) { + OverlayPlatformPlugin::InitPlatformOverlay(std::move(overlay_listener), id, + std::move(tag), recording_plugin); + if (!engine_ || !engine_->view() || !manager_) { + return; + } + node_id_ = id; + EnsureView(); +} + +void OverlayPlatformPluginWin::EnsureView() { + if (view_ || node_id_ == -1 || preferred_width_ <= 0 || + preferred_height_ <= 0 || !engine_ || !engine_->view() || !manager_) { + return; + } + OverlayWindowCreationRequest request = { + .preferred_size = {preferred_width_, preferred_height_}, + .title = L"cover-view"}; + view_ = manager_->CreateView(node_id_, OverlayWindowType::kChild, request, + engine_->view()->GetWindowHandle()); + if (view_) { + view_->SetEventThrough(event_through_); + } +} + +std::unique_ptr +OverlayPlatformServiceWin::CreateOverlayPlatformPlugin() { + if (!overlay_view_manager_service_) { + return nullptr; + } + return std::make_unique( + overlay_view_manager_service_->GetEngine(), + overlay_view_manager_service_->GetOverlayWindowManager()); +} + +void OverlayPlatformServiceWin::OnInit(ServiceManager& service_manager, + const PlatformServiceContext& ctx) { + overlay_view_manager_service_ = + service_manager.GetService(); +} + +void OverlayPlatformServiceWin::OnDestroy() { + overlay_view_manager_service_ = nullptr; +} + +} // namespace clay diff --git a/clay/shell/platform/windows/overlay_platform_plugin_win.h b/clay/shell/platform/windows/overlay_platform_plugin_win.h new file mode 100644 index 0000000000..dd788f0a9a --- /dev/null +++ b/clay/shell/platform/windows/overlay_platform_plugin_win.h @@ -0,0 +1,71 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef CLAY_SHELL_PLATFORM_WINDOWS_OVERLAY_PLATFORM_PLUGIN_WIN_H_ +#define CLAY_SHELL_PLATFORM_WINDOWS_OVERLAY_PLATFORM_PLUGIN_WIN_H_ + +#include +#include + +#include "clay/common/service/service_manager.h" +#include "clay/shell/platform/windows/overlay_view_manager_service.h" +#include "clay/ui/platform/overlay_service.h" + +namespace clay { + +class OverlayPlatformPluginWin final : public OverlayPlatformPlugin { + public: + OverlayPlatformPluginWin(FlutterWindowsEngine* engine, + OverlayViewManager* manager); + ~OverlayPlatformPluginWin() override = default; + + void ChangeVisibility(bool visible) override; + void SetLevel(int level) override {} + void SetCutOutMode(bool is_cut_out) override {} + void SetAndroidSoftInputMode(std::string mode) override {} + void SetAndroidNativeEventPass(bool is_pass) override {} + void SetStatusBarTranslucent(bool is_translucent) override {} + void SetStatusBarTranslucentStyle(std::string style) override {} + void SetAndroidFullScreen(bool is_full_screen) override {} + void SetEventThrough(bool event_through) override; + void SetSize(int width, int height) override; + + bool ShouldHandleTreeLifecycle() const override { return true; } + bool RequiresExternalViewPlugin() const override { return false; } + void OnAttachToTree() override {} + void OnDetachFromTree() override; + void OnViewDestroy() override; + + void InitPlatformOverlay( + std::shared_ptr>> overlay_listener, + int id, std::string tag, ExternalViewPlugin* recording_plugin) override; + + private: + void EnsureView(); + + int64_t node_id_ = -1; + int preferred_width_ = 0; + int preferred_height_ = 0; + bool event_through_ = false; + FlutterWindowsEngine* engine_ = nullptr; + OverlayViewManager* manager_ = nullptr; + std::shared_ptr view_; +}; + +class OverlayPlatformServiceWin final : public OverlayService { + public: + std::unique_ptr CreateOverlayPlatformPlugin() override; + + void OnInit(ServiceManager& service_manager, + const PlatformServiceContext& ctx) override; + void OnDestroy() override; + + private: + Puppet + overlay_view_manager_service_; +}; + +} // namespace clay + +#endif // CLAY_SHELL_PLATFORM_WINDOWS_OVERLAY_PLATFORM_PLUGIN_WIN_H_ diff --git a/clay/shell/platform/windows/overlay_view_controller.cc b/clay/shell/platform/windows/overlay_view_controller.cc index 433cf0ea9f..54fe4d6078 100644 --- a/clay/shell/platform/windows/overlay_view_controller.cc +++ b/clay/shell/platform/windows/overlay_view_controller.cc @@ -16,10 +16,11 @@ OverlayViewController::OverlayViewController( const std::wstring& title) : engine_(engine) { if (type == OverlayWindowType::kChild) { - std::unique_ptr window_wrapper = + auto window_wrapper = std::make_unique(parent, CW_USEDEFAULT, CW_USEDEFAULT, preferred_size.preferred_view_width, preferred_size.preferred_view_height); + child_window_ = window_wrapper.get(); child_view_ = engine_->CreateOverlayView(std::move(window_wrapper)); overlay_view_ = child_view_.get(); ShowWindow(child_view_->GetWindowHandle(), SW_HIDE); @@ -45,6 +46,12 @@ OverlayWindowType OverlayViewController::GetType() { return type_; } FlutterWindowsView* OverlayViewController::GetView() { return overlay_view_; } +void OverlayViewController::PrepareSurfaceSize(int width, int height) { + if (overlay_view_ && width > 0 && height > 0) { + overlay_view_->PrepareSurfaceSize(width, height); + } +} + void OverlayViewController::UpdatePosition(int left, int top, int width, int height) { if (child_view_) { @@ -53,4 +60,10 @@ void OverlayViewController::UpdatePosition(int left, int top, int width, } } +void OverlayViewController::SetEventThrough(bool event_through) { + if (child_window_) { + child_window_->SetEventThrough(event_through); + } +} + } // namespace clay diff --git a/clay/shell/platform/windows/overlay_view_controller.h b/clay/shell/platform/windows/overlay_view_controller.h index 9ef308d533..0ed713af71 100644 --- a/clay/shell/platform/windows/overlay_view_controller.h +++ b/clay/shell/platform/windows/overlay_view_controller.h @@ -31,10 +31,13 @@ class OverlayViewController { FlutterWindowsView* GetView(); OverlayWindowType GetType(); + void PrepareSurfaceSize(int width, int height); void UpdatePosition(int left, int top, int width, int height); + void SetEventThrough(bool event_through); private: std::unique_ptr child_view_; + FlutterWindow* child_window_ = nullptr; OverlayWindowType type_; FlutterWindowsView* overlay_view_ = nullptr; FlutterWindowsEngine* engine_; diff --git a/clay/shell/platform/windows/overlay_view_manager_service.cc b/clay/shell/platform/windows/overlay_view_manager_service.cc index a37a472ca4..3bbe3ebcdc 100644 --- a/clay/shell/platform/windows/overlay_view_manager_service.cc +++ b/clay/shell/platform/windows/overlay_view_manager_service.cc @@ -18,6 +18,9 @@ OverlayViewManager::OverlayViewManager(FlutterWindowsEngine* engine) std::shared_ptr OverlayViewManager::CreateView( int64_t overlay_id, OverlayWindowType type, const OverlayWindowCreationRequest& request, HWND parent_handle) { + if (auto existing_view = GetView(overlay_id)) { + return existing_view; + } auto view = std::make_shared( engine_, type, request.preferred_size, parent_handle, L"ClayOverlayView"); { diff --git a/clay/shell/platform/windows/platform_overlay_service_win.cc b/clay/shell/platform/windows/platform_overlay_service_win.cc index 671a83d4eb..da0525abfd 100644 --- a/clay/shell/platform/windows/platform_overlay_service_win.cc +++ b/clay/shell/platform/windows/platform_overlay_service_win.cc @@ -78,16 +78,16 @@ void PlatformOverlayWin::PrepareSurface(const OverlayData& data) { if (view == nullptr) { view = manager_->CreateView(view_id, OverlayWindowType::kChild, request, engine_->view()->GetWindowHandle()); - } else { - manager_->RemoveView(view_id); - view = manager_->CreateView(view_id, OverlayWindowType::kChild, request, - engine_->view()->GetWindowHandle()); } view_id_.store(view_id); latch.CountDown(); }); latch.Wait(); } + auto view = manager_->GetView(view_id); + if (view) { + view->PrepareSurfaceSize(width, height); + } } std::unique_ptr PlatformOverlayWin::GLContextMakeCurrent() { diff --git a/clay/shell/platform/windows/window.cc b/clay/shell/platform/windows/window.cc index 1f660764d0..f0dc5f563a 100644 --- a/clay/shell/platform/windows/window.cc +++ b/clay/shell/platform/windows/window.cc @@ -297,6 +297,11 @@ Window::HandleMessage(UINT const message, WPARAM const wparam, ClayPointerDeviceKind device_kind; switch (message) { + case WM_NCHITTEST: + if (event_through_) { + return HTTRANSPARENT; + } + break; case kWmDpiChangedBeforeParent: current_dpi_ = GetDpiForHWND(window_handle_); OnDpiScale(current_dpi_); @@ -584,6 +589,10 @@ UINT Window::GetCurrentHeight() { return current_height_; } HWND Window::GetWindowHandle() { return window_handle_; } +void Window::SetEventThrough(bool event_through) { + event_through_ = event_through; +} + float Window::GetScrollOffsetMultiplier() { return scroll_offset_multiplier_ * current_dpi_ / 96.0; } diff --git a/clay/shell/platform/windows/window.h b/clay/shell/platform/windows/window.h index 3ade2a417a..45d0589138 100644 --- a/clay/shell/platform/windows/window.h +++ b/clay/shell/platform/windows/window.h @@ -42,6 +42,7 @@ class Window : public KeyboardManager::WindowDelegate { unsigned int y, unsigned int width, unsigned int height); HWND GetWindowHandle(); + void SetEventThrough(bool event_through); // |KeyboardManager::WindowDelegate| virtual BOOL Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin, @@ -234,6 +235,8 @@ class Window : public KeyboardManager::WindowDelegate { // Set to true to be notified when the mouse leaves the window. bool tracking_mouse_leave_ = false; + bool event_through_ = false; + // Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN // message. int keycode_for_char_message_ = 0; diff --git a/clay/ui/component/base_view.cc b/clay/ui/component/base_view.cc index d0409c1b8b..671c9868fe 100644 --- a/clay/ui/component/base_view.cc +++ b/clay/ui/component/base_view.cc @@ -74,18 +74,6 @@ LYNX_UI_METHOD_END(BaseView); constexpr int64_t FORCE_CACHE_ANIMATION_DURATION = 500; -bool ShouldPassEventToNativeInherited(BaseView* view) { - if (view == nullptr) { - return false; - } else if (view->CanEventThrough().has_value()) { - return *view->CanEventThrough(); - } else if (view->Parent() == nullptr) { - return false; - } else { - return ShouldPassEventToNativeInherited(view->Parent()); - } -} - #ifdef ENABLE_ACCESSIBILITY BaseView* A11yScrollTargetForSemantics(BaseView* view) { if (!view) { @@ -2809,7 +2797,7 @@ bool BaseView::HitTest(const PointerEvent& event, HitTestResult& result) { if (beyond_self) { return founded; } - should_pass_event_for_hittest_ = ShouldPassEventToNativeInherited(this); + should_pass_event_for_hittest_ = ShouldPassEventToNativeInherited(); result.emplace_back(GetHitTestTargetWeakPtr()); return true; } @@ -3026,11 +3014,22 @@ BaseView* BaseView::GetTopViewToAcceptEvent(const FloatPoint& position, return nullptr; } *relative_position = point_by_self; - return ShouldPassEventToNativeInherited(this) ? nullptr : this; + return ShouldPassEventToNativeInherited() ? nullptr : this; } return nullptr; } +bool BaseView::ShouldPassEventToNativeInherited() const { + const BaseView* view = this; + while (view != nullptr) { + if (view->CanEventThrough().has_value()) { + return *view->CanEventThrough(); + } + view = view->Parent(); + } + return false; +} + FloatPoint BaseView::GetPointBySelf(const FloatPoint& point_by_page) const { FloatPoint point = point_by_page; BaseView* parent = Parent(); @@ -3190,8 +3189,12 @@ bool BaseView::HandleCommonAttribute(const char* attr, data_set_ = CloneClayValue(value); break; case KeywordID::kEventThrough: - event_through_ = utils::GetBool(value); - if (event_through_) { + if (utils::IsNullOrInvalid(value)) { + event_through_.reset(); + } else { + event_through_ = utils::GetBool(value); + } + if (event_through_.value_or(false)) { auto task_runners = page_view_->GetTaskRunners(); if (task_runners.GetPlatformTaskRunner() != task_runners.GetUITaskRunner()) { diff --git a/clay/ui/component/base_view.h b/clay/ui/component/base_view.h index d85302eca9..ff1cc6243d 100644 --- a/clay/ui/component/base_view.h +++ b/clay/ui/component/base_view.h @@ -592,8 +592,11 @@ class BaseView : public TypeIdentifiable, void CheckStickyOnParentScrollAndReset(int left, int top); void SetEventThrough(bool event_through) { event_through_ = event_through; } - // this means whether the entire page through the touch events. + // Returns the value explicitly set on this node, or nullopt when the value + // should be inherited from its parent. std::optional CanEventThrough() const { return event_through_; } + // Resolves event-through using the node inheritance rules. + bool ShouldPassEventToNativeInherited() const; // this means whether this view node pass through the events to the nodes // behind it. virtual bool CanEventsPassThroughToViewsBehind() const { return false; } diff --git a/clay/ui/component/build.gni b/clay/ui/component/build.gni index 096c4666e1..ab7902f5df 100644 --- a/clay/ui/component/build.gni +++ b/clay/ui/component/build.gni @@ -144,6 +144,13 @@ if (is_mac || is_win) { ] } +if (is_win) { + ui_component_sources += [ + "cover_view_platform_delegate.cc", + "cover_view_platform_delegate.h", + ] +} + if (enable_svg) { ui_component_sources += [ "svg_image_view.cc", diff --git a/clay/ui/component/cover_view.cc b/clay/ui/component/cover_view.cc index 050060151a..8976e86f2e 100644 --- a/clay/ui/component/cover_view.cc +++ b/clay/ui/component/cover_view.cc @@ -4,20 +4,96 @@ #include "clay/ui/component/cover_view.h" +#include #include +#include "build/build_config.h" +#if OS_WIN +#include "clay/ui/common/attribute_utils.h" +#include "clay/ui/component/cover_view_platform_delegate.h" +#include "clay/ui/component/keywords.h" +#include "clay/ui/component/page_view.h" +#endif #include "clay/ui/rendering/render_external_view.h" namespace clay { CoverView::CoverView(int id, PageView* page_view) : WithTypeInfo(id, "cover-view", std::make_unique(), - page_view) {} + page_view) { +#if OS_WIN + platform_delegate_ = + std::make_unique(id, page_view); +#endif +} + +#if OS_WIN +CoverView::~CoverView() = default; + +void CoverView::SetAttribute(const char* attr, const clay::Value& value) { + BaseView::SetAttribute(attr, value); + if (GetKeywordID(attr) == KeywordID::kEventThrough && platform_delegate_) { + platform_delegate_->SetEventThrough(attribute_utils::GetBool(value)); + } +} + +bool CoverView::HitTest(const PointerEvent& event, HitTestResult& result) { + HitTestResult cover_result; + if (!BaseView::HitTest(event, cover_result)) { + return false; + } + + for (const auto& target : cover_result) { + if (!target) { + continue; + } + if (target->ShouldPassEventToNative()) { + return false; + } + break; + } + + result.splice(result.end(), cover_result); + return true; +} +#endif void CoverView::SetBound(float left, float top, float width, float height) { BaseView::SetBound(left, top, width, height); static_cast(render_object_.get()) ->SetBackingSize(skity::Vec2(width, height)); +#if OS_WIN + if (platform_delegate_ && page_view()) { + const auto physical_bounds = page_view()->ConvertTo( + FloatRect(left, top, width, height)); + platform_delegate_->SetSize( + static_cast(std::ceil(physical_bounds.width())), + static_cast(std::ceil(physical_bounds.height()))); + } +#endif +} + +#if OS_WIN +void CoverView::OnAttachToTree() { + BaseView::OnAttachToTree(); + if (platform_delegate_) { + platform_delegate_->OnAttachToTree(); + } +} + +void CoverView::OnDetachFromTree() { + if (platform_delegate_) { + platform_delegate_->OnDetachFromTree(); + } + BaseView::OnDetachFromTree(); +} + +void CoverView::OnDestroy() { + if (platform_delegate_) { + platform_delegate_->OnDestroy(); + } + BaseView::OnDestroy(); } +#endif } // namespace clay diff --git a/clay/ui/component/cover_view.h b/clay/ui/component/cover_view.h index aafb8e30e1..34fc9ecdf8 100644 --- a/clay/ui/component/cover_view.h +++ b/clay/ui/component/cover_view.h @@ -5,16 +5,35 @@ #ifndef CLAY_UI_COMPONENT_COVER_VIEW_H_ #define CLAY_UI_COMPONENT_COVER_VIEW_H_ +#include + +#include "build/build_config.h" #include "clay/ui/component/overlay_view.h" namespace clay { +class CoverViewPlatformDelegate; + class CoverView : public WithTypeInfo { public: CoverView(int id, PageView* page_view); + void SetBound(float left, float top, float width, float height) override; +#if OS_WIN + ~CoverView() override; + void SetAttribute(const char* attr, const clay::Value& value) override; + bool HitTest(const PointerEvent& event, HitTestResult& result) override; + void OnAttachToTree() override; + void OnDetachFromTree() override; + void OnDestroy() override; +#endif bool IsLayoutRootCandidate() const override { return true; } + + private: +#if OS_WIN + std::unique_ptr platform_delegate_; +#endif }; } // namespace clay diff --git a/clay/ui/component/cover_view_platform_delegate.cc b/clay/ui/component/cover_view_platform_delegate.cc new file mode 100644 index 0000000000..f19183e9fc --- /dev/null +++ b/clay/ui/component/cover_view_platform_delegate.cc @@ -0,0 +1,68 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "clay/ui/component/cover_view_platform_delegate.h" + +#include + +#include "clay/common/service/service_manager.h" +#include "clay/ui/component/page_view.h" + +namespace clay { + +CoverViewPlatformDelegate::CoverViewPlatformDelegate(int id, + PageView* page_view) + : weak_factory_(this) { + if (!page_view || !page_view->GetServiceManager()) { + return; + } + Puppet overlay_service = + page_view->GetServiceManager()->GetService(); + if (!overlay_service) { + return; + } + overlay_plugin_ = overlay_service.CreateObjectInActorThread( + [](auto& service) { return service.CreateOverlayPlatformPlugin(); }); + overlay_plugin_.PostObjectToActorThread( + fml::WeakPtr(GetWeakPtr()), + [id](auto& plugin, auto listener) { + plugin.InitPlatformOverlay(std::move(listener), id, "cover-view", + nullptr); + }); +} + +void CoverViewPlatformDelegate::SetEventThrough(bool event_through) { + if (overlay_plugin_) { + overlay_plugin_.Act([event_through](auto& plugin) { + plugin.SetEventThrough(event_through); + }); + } +} + +void CoverViewPlatformDelegate::SetSize(int width, int height) { + if (overlay_plugin_ && width > 0 && height > 0) { + overlay_plugin_.Act( + [width, height](auto& plugin) { plugin.SetSize(width, height); }); + } +} + +void CoverViewPlatformDelegate::OnAttachToTree() { + if (overlay_plugin_) { + overlay_plugin_.Act([](auto& plugin) { plugin.OnAttachToTree(); }); + } +} + +void CoverViewPlatformDelegate::OnDetachFromTree() { + if (overlay_plugin_) { + overlay_plugin_.Act([](auto& plugin) { plugin.OnDetachFromTree(); }); + } +} + +void CoverViewPlatformDelegate::OnDestroy() { + if (overlay_plugin_) { + overlay_plugin_.Act([](auto& plugin) { plugin.OnViewDestroy(); }); + } +} + +} // namespace clay diff --git a/clay/ui/component/cover_view_platform_delegate.h b/clay/ui/component/cover_view_platform_delegate.h new file mode 100644 index 0000000000..b68aa9a43e --- /dev/null +++ b/clay/ui/component/cover_view_platform_delegate.h @@ -0,0 +1,44 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef CLAY_UI_COMPONENT_COVER_VIEW_PLATFORM_DELEGATE_H_ +#define CLAY_UI_COMPONENT_COVER_VIEW_PLATFORM_DELEGATE_H_ + +#include + +#include "base/include/fml/memory/weak_ptr.h" +#include "clay/common/service/service.h" +#include "clay/ui/platform/overlay_service.h" + +namespace clay { + +class PageView; + +class CoverViewPlatformDelegate final + : public OverlayPlatformPlugin::OverlayListener { + public: + CoverViewPlatformDelegate(int id, PageView* page_view); + ~CoverViewPlatformDelegate() override = default; + + void SetEventThrough(bool event_through); + void SetSize(int width, int height); + void OnAttachToTree(); + void OnDetachFromTree(); + void OnDestroy(); + + void OnDialogBackPressed() override {} + void OnViewOffsetUpdated(int offset_x, int offset_y) override {} + + private: + fml::WeakPtr GetWeakPtr() const { + return weak_factory_.GetWeakPtr(); + } + + fml::WeakPtrFactory weak_factory_; + Puppet> overlay_plugin_; +}; + +} // namespace clay + +#endif // CLAY_UI_COMPONENT_COVER_VIEW_PLATFORM_DELEGATE_H_ diff --git a/clay/ui/component/text/text_view.cc b/clay/ui/component/text/text_view.cc index 4c2b95b6d2..483cab3fd0 100644 --- a/clay/ui/component/text/text_view.cc +++ b/clay/ui/component/text/text_view.cc @@ -729,7 +729,8 @@ BaseView* TextView::GetTopViewToAcceptEvent(const FloatPoint& position, *relative_position = point_by_paragraph; BaseView* view = nullptr; view = GetViewAtPosition(point_by_paragraph, position, platform_try_hit_id); - return view ?: this; + BaseView* target = view ?: this; + return target->ShouldPassEventToNativeInherited() ? nullptr : target; } BaseView* TextView::GetViewAtPosition(const FloatPoint& point_by_paragraph, diff --git a/clay/ui/platform/build.gni b/clay/ui/platform/build.gni index 9083ab38e4..547dce08cc 100644 --- a/clay/ui/platform/build.gni +++ b/clay/ui/platform/build.gni @@ -12,6 +12,7 @@ ui_platform_sources = [ "keyboard_types.h", "native_view_service.h", "native_view_tags.h", + "overlay_service.h", "text_emoji_provider.h", ] diff --git a/clay/ui/platform/overlay_service.h b/clay/ui/platform/overlay_service.h new file mode 100644 index 0000000000..f41ad6c3d5 --- /dev/null +++ b/clay/ui/platform/overlay_service.h @@ -0,0 +1,66 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef CLAY_UI_PLATFORM_OVERLAY_SERVICE_H_ +#define CLAY_UI_PLATFORM_OVERLAY_SERVICE_H_ + +#include +#include +#include + +#include "clay/common/service/service.h" + +namespace clay { + +class ExternalViewPlugin; + +class OverlayPlatformPlugin : public ActorObject { + public: + class OverlayListener : public ActorObject { + public: + virtual ~OverlayListener() = default; + virtual void OnDialogBackPressed() = 0; + virtual void OnViewOffsetUpdated(int offset_x, int offset_y) = 0; + virtual void OnViewSizeUpdated(int width, int height) {} + }; + + virtual ~OverlayPlatformPlugin() = default; + + virtual void ChangeVisibility(bool visible) = 0; + virtual void SetLevel(int level) = 0; + virtual void SetCutOutMode(bool is_cut_out) = 0; + virtual void SetAndroidSoftInputMode(std::string mode) = 0; + virtual void SetAndroidNativeEventPass(bool is_pass) = 0; + virtual void SetStatusBarTranslucent(bool is_translucent) = 0; + virtual void SetStatusBarTranslucentStyle(std::string style) = 0; + virtual void SetAndroidFullScreen(bool is_full_screen) = 0; + virtual void SetEventThrough(bool event_through) {} + virtual void SetSize(int width, int height) {} + + virtual bool ShouldHandleTreeLifecycle() const { return false; } + virtual bool RequiresExternalViewPlugin() const { return true; } + virtual void OnAttachToTree() = 0; + virtual void OnDetachFromTree() = 0; + virtual void OnViewDestroy() = 0; + + virtual void InitPlatformOverlay( + std::shared_ptr>> overlay_listener, + int id, std::string tag, ExternalViewPlugin* recording_plugin) { + overlay_listener_ = std::move(overlay_listener); + } + + protected: + Puppet> overlay_listener_; +}; + +class OverlayService : public Service { + public: + virtual std::unique_ptr + CreateOverlayPlatformPlugin() = 0; +}; + +} // namespace clay + +#endif // CLAY_UI_PLATFORM_OVERLAY_SERVICE_H_ diff --git a/core/public/perf_controller_proxy.h b/core/public/perf_controller_proxy.h index dfe0088abd..98db97260a 100644 --- a/core/public/perf_controller_proxy.h +++ b/core/public/perf_controller_proxy.h @@ -15,6 +15,12 @@ namespace lynx { namespace shell { +#if defined(ReportEvent) +#define LYNX_CORE_PUBLIC_PERF_CONTROLLER_PROXY_RESTORE_REPORT_EVENT_MACRO_ +#pragma push_macro("ReportEvent") +#undef ReportEvent +#endif + /** * @brief Event struct containing different types of property */ @@ -25,6 +31,15 @@ struct ReportEvent { std::unordered_map double_props; }; +// Windows defines ReportEvent as a macro. Use this alias in declarations that +// can be parsed after Windows headers while retaining the existing public type. +using PerfReportEvent = ReportEvent; + +#if defined(LYNX_CORE_PUBLIC_PERF_CONTROLLER_PROXY_RESTORE_REPORT_EVENT_MACRO_) +#pragma pop_macro("ReportEvent") +#undef LYNX_CORE_PUBLIC_PERF_CONTROLLER_PROXY_RESTORE_REPORT_EVENT_MACRO_ +#endif + class PerfControllerProxy { public: virtual ~PerfControllerProxy() = default; @@ -77,7 +92,7 @@ class PerfControllerProxy { * @param instance_id The instanceId of a lynx view * @param event The event to be reported */ - virtual void OnEvent(int32_t instance_id, ReportEvent& event) = 0; + virtual void OnEvent(int32_t instance_id, PerfReportEvent& event) = 0; }; } // namespace shell diff --git a/core/shell/perf_controller_proxy_impl.cc b/core/shell/perf_controller_proxy_impl.cc index e46b5fc495..01d0cf3113 100644 --- a/core/shell/perf_controller_proxy_impl.cc +++ b/core/shell/perf_controller_proxy_impl.cc @@ -71,7 +71,8 @@ void PerfControllerProxyImpl::RunTaskInReportThread(base::closure task) { }); } -void PerfControllerProxyImpl::OnEvent(int32_t instance_id, ReportEvent& event) { +void PerfControllerProxyImpl::OnEvent(int32_t instance_id, + PerfReportEvent& event) { perf_actor_->ActAsync( [instance_id, event = std::move(event)](auto& controller) mutable { lynx::tasm::report::MoveOnlyEvent move_only_event; diff --git a/core/shell/perf_controller_proxy_impl.h b/core/shell/perf_controller_proxy_impl.h index b0f652457f..3de6aeadec 100644 --- a/core/shell/perf_controller_proxy_impl.h +++ b/core/shell/perf_controller_proxy_impl.h @@ -35,7 +35,7 @@ class PerfControllerProxyImpl : public PerfControllerProxy { void RunTaskInReportThread(base::closure task) override; - void OnEvent(int32_t instance_id, ReportEvent& event) override; + void OnEvent(int32_t instance_id, PerfReportEvent& event) override; protected: std::shared_ptr>