diff --git a/BUILD.gn b/BUILD.gn index aed14fb910e2371fe5353721c144924e99989790..c36670ad7d9fd3e753e4b55c7886b67cc5b8e136 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -164,8 +164,10 @@ group("flutter") { if (is_win) { if (target_os == "winuwp") { - # TODO: Add winnup variant of the unit tests here; see + # TODO: Add winnup variant of client_wrapper_windows_unittests here; see # https://github.com/flutter/flutter/issues/70197 + public_deps += + [ "//flutter/shell/platform/windows:flutter_windows_unittests" ] } else { public_deps += [ "//flutter/shell/platform/windows:flutter_windows_unittests", diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 03d9753e74c472bcf917f2bd9b5299f686f01e76..c9a88a4807eaf7490908fe88c9c167b35dcf27c8 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1462,6 +1462,8 @@ FILE: ../../../flutter/shell/platform/windows/external_texture_gl.h FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.cc FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.h FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle_unittests.cc +FILE: ../../../flutter/shell/platform/windows/flutter_window_winuwp.cc +FILE: ../../../flutter/shell/platform/windows/flutter_window_winuwp.h FILE: ../../../flutter/shell/platform/windows/flutter_windows.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_engine.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_engine.h diff --git a/shell/platform/common/cpp/engine_switches.cc b/shell/platform/common/cpp/engine_switches.cc index f19d43cbb5fd252fc34a5cea58a0037950e2d181..52bafc8738fa072dcb4855b68cfdfe693d5918e5 100644 --- a/shell/platform/common/cpp/engine_switches.cc +++ b/shell/platform/common/cpp/engine_switches.cc @@ -16,6 +16,9 @@ std::vector GetSwitchesFromEnvironment() { // Read engine switches from the environment in debug/profile. If release mode // support is needed in the future, it should likely use a whitelist. #ifndef FLUTTER_RELEASE +// TODO(clarkezone): figure out how to handle engine switches in UWP mode +// https://github.com/flutter/flutter/issues/74153 +#ifndef WINUWP const char* switch_count_key = "FLUTTER_ENGINE_SWITCHES"; const int kMaxSwitchCount = 50; const char* switch_count_string = std::getenv(switch_count_key); @@ -36,6 +39,7 @@ std::vector GetSwitchesFromEnvironment() { << ", but " << switch_key.str() << " is missing." << std::endl; } } +#endif // !WINUWP #endif // !FLUTTER_RELEASE return switches; } diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 86c4190972f059a06b484f608103c0af6d24832e..0411b8dafa7c07d1ee08f4339f821f978cae92e2 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -78,6 +78,8 @@ source_set("flutter_windows_source") { # Target-specific sources. if (target_os == "winuwp") { sources += [ + "flutter_window_winuwp.cc", + "flutter_window_winuwp.h", "flutter_windows_winuwp.cc", "platform_handler_winuwp.cc", "platform_handler_winuwp.h", @@ -118,7 +120,10 @@ source_set("flutter_windows_source") { public_configs = [ ":relative_angle_headers" ] - defines = [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] + defines = [ + "FLUTTER_ENGINE_NO_PROTOTYPES", + "USECOREWINDOW", + ] deps = [ ":flutter_windows_headers", @@ -162,20 +167,33 @@ test_fixtures("flutter_windows_fixtures") { fixtures = [] } -if (target_os == "winuwp") { - # disabled until the uwp implementation is present -} else { - executable("flutter_windows_unittests") { - testonly = true +executable("flutter_windows_unittests") { + testonly = true + + if (target_os == "winuwp") { + libs = [ "windowsapp.lib" ] + } - sources = [ + # Common Windows test sources. + sources = [ + # "flutter_project_bundle_unittests.cc", //TODO failing due to switches test failing. Blocked on https://github.com/flutter/flutter/issues/74153 + # "flutter_windows_engine_unittests.cc", //TODO failing to send / receive platform message get plugins working first. Blocked on https://github.com/flutter/flutter/issues/74155 + "string_conversion_unittests.cc", + "system_utils_unittests.cc", + "testing/engine_embedder_api_modifier.h", + ] + + # Target-specific sources. + if (target_os == "winuwp") { + # TODO(clarkezone) add UWP tests + # https://github.com/flutter/flutter/issues/70197 + } else { + sources += [ + # TODO move first two tests to common once above TODO's unblocked. "flutter_project_bundle_unittests.cc", "flutter_windows_engine_unittests.cc", "flutter_windows_texture_registrar_unittests.cc", "key_event_handler_unittests.cc", - "string_conversion_unittests.cc", - "system_utils_unittests.cc", - "testing/engine_embedder_api_modifier.h", "testing/mock_win32_window.cc", "testing/mock_win32_window.h", "testing/mock_window_binding_handler.cc", @@ -187,20 +205,20 @@ if (target_os == "winuwp") { "win32_window_proc_delegate_manager_unittests.cc", "win32_window_unittests.cc", ] + } - public_configs = [ "//flutter:config" ] + public_configs = [ "//flutter:config" ] - deps = [ - ":flutter_windows_fixtures", - ":flutter_windows_headers", - ":flutter_windows_source", - "//flutter/shell/platform/common/cpp:common_cpp", - "//flutter/shell/platform/embedder:embedder_as_internal_library", - "//flutter/shell/platform/embedder:embedder_test_utils", - "//flutter/testing", - "//third_party/rapidjson", - ] - } + deps = [ + ":flutter_windows_fixtures", + ":flutter_windows_headers", + ":flutter_windows_source", + "//flutter/shell/platform/common/cpp:common_cpp", + "//flutter/shell/platform/embedder:embedder_as_internal_library", + "//flutter/shell/platform/embedder:embedder_test_utils", + "//flutter/testing", + "//third_party/rapidjson", + ] } shared_library("flutter_windows_glfw") { diff --git a/shell/platform/windows/angle_surface_manager.cc b/shell/platform/windows/angle_surface_manager.cc index 32c2bc3de0d2b82061d21b8dceb0191821cff6c9..6862e3afb31316bb206ec912adc16095cabbef8a 100644 --- a/shell/platform/windows/angle_surface_manager.cc +++ b/shell/platform/windows/angle_surface_manager.cc @@ -7,6 +7,15 @@ #include #include +#ifdef WINUWP +#include +#include +#endif + +#if defined(WINUWP) && defined(USECOREWINDOW) +#include +#endif + namespace flutter { AngleSurfaceManager::AngleSurfaceManager() @@ -188,10 +197,23 @@ bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target, EGL_FIXED_SIZE_ANGLE, EGL_TRUE, EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; +#ifdef WINUWP +#ifdef USECOREWINDOW + auto target = std::get(*render_target); +#else + auto target = + std::get(*render_target); +#endif + surface = eglCreateWindowSurface( + egl_display_, egl_config_, + static_cast(winrt::get_abi(target)), + surfaceAttributes); +#else surface = eglCreateWindowSurface( egl_display_, egl_config_, static_cast(std::get(*render_target)), surfaceAttributes); +#endif if (surface == EGL_NO_SURFACE) { std::cerr << "Surface creation failed." << std::endl; } @@ -212,6 +234,8 @@ void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target, // preserve the previous surface contents. This resize approach could be // further optimized if Angle exposed a public entrypoint for // SwapChain11::reset or SwapChain11::resize. + // a possible starting point for that could build on + // eglPostSubBufferNV(egl_display_, render_surface_, 1, 1, width, height); DestroySurface(); if (!CreateSurface(render_target, width, height)) { std::cerr << "AngleSurfaceManager::ResizeSurface failed to create surface" diff --git a/shell/platform/windows/client_wrapper/flutter_view_controller.cc b/shell/platform/windows/client_wrapper/flutter_view_controller.cc index 2724b27881f2220bf1d4dddf83c9cb629340d49e..a4c5a465a1c2118fa7b1fe9bad9f19205674cccd 100644 --- a/shell/platform/windows/client_wrapper/flutter_view_controller.cc +++ b/shell/platform/windows/client_wrapper/flutter_view_controller.cc @@ -9,6 +9,21 @@ namespace flutter { +#ifdef WINUWP +FlutterViewController::FlutterViewController( + ABI::Windows::UI::Core::CoreWindow* window, + const DartProject& project) { + engine_ = std::make_unique(project); + controller_ = FlutterDesktopViewControllerCreateFromCoreWindow( + window, engine_->RelinquishEngine()); + if (!controller_) { + std::cerr << "Failed to create view controller." << std::endl; + return; + } + view_ = std::make_unique( + FlutterDesktopViewControllerGetView(controller_)); +} +#else FlutterViewController::FlutterViewController(int width, int height, const DartProject& project) { @@ -22,6 +37,7 @@ FlutterViewController::FlutterViewController(int width, view_ = std::make_unique( FlutterDesktopViewControllerGetView(controller_)); } +#endif FlutterViewController::~FlutterViewController() { if (controller_) { diff --git a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h index 9dff236ff2cd56bbbd461bd8ea9547cf69a3b3ed..e0ff9d7f23547b2880ef8b7b80791974f30ff3bf 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h +++ b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h @@ -13,6 +13,22 @@ namespace flutter { // A set of Flutter and Dart assets used to initialize a Flutter engine. class DartProject { public: +#ifdef WINUWP + // Creates a DartProject from a series of absolute paths. + // The directory should contain the following top-level items: + // - icudtl.dat (provided as a resource by the Flutter tool) + // - flutter_assets (as built by the Flutter tool) + // - app.so, for an AOT build (as built by the Flutter tool) + // + // The path must be absolute. + explicit DartProject(const std::wstring& assetspath, + const std::wstring& icupath, + const std::wstring& aotpath) { + assets_path_ = assetspath; + icu_data_path_ = icupath; + aot_library_path_ = aotpath; + } +#else // Creates a DartProject from a directory path. The directory should contain // the following top-level items: // - icudtl.dat (provided as a resource by the Flutter tool) @@ -26,6 +42,7 @@ class DartProject { icu_data_path_ = path + L"\\icudtl.dat"; aot_library_path_ = path + L"\\app.so"; } +#endif ~DartProject() = default; diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h index 18c908e977deb13b4e24441eab5c4849c3e62784..057d905308eb42779aabac678d31f7502417f61a 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h @@ -20,8 +20,10 @@ class FlutterView { FlutterView(FlutterView const&) = delete; FlutterView& operator=(FlutterView const&) = delete; +#ifndef WINUWP // Returns the backing HWND for the view. HWND GetNativeWindow() { return FlutterDesktopViewGetHWND(view_); } +#endif private: // Handle for interacting with the C API's view. diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h index f39a7e70c980ef1e7c3c36e36d74f2d8518906fc..dcab4a7f8422973c4c3993c9a50963e391b37244 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h @@ -17,6 +17,10 @@ #include "plugin_registrar.h" #include "plugin_registry.h" +#ifdef WINUWP +#include +#endif + namespace flutter { // A controller for a view displaying Flutter content. @@ -26,13 +30,22 @@ namespace flutter { // methods in the C API directly, as this class will do that internally. class FlutterViewController { public: +#ifndef WINUWP // Creates a FlutterView that can be parented into a Windows View hierarchy - // either using HWNDs or in the future into a CoreWindow, or using compositor. + // either using HWNDs. // // |dart_project| will be used to configure the engine backing this view. explicit FlutterViewController(int width, int height, const DartProject& project); +#else + // Creates a FlutterView that can be parented into a Windows View hierarchy + // either using CoreWindow. + // + // |dart_project| will be used to configure the engine backing this view. + explicit FlutterViewController(ABI::Windows::UI::Core::CoreWindow* window, + const DartProject& project); +#endif virtual ~FlutterViewController(); diff --git a/shell/platform/windows/flutter_window_winuwp.cc b/shell/platform/windows/flutter_window_winuwp.cc new file mode 100644 index 0000000000000000000000000000000000000000..8593b8580cd72d65bebb6e354aacf4b343994e00 --- /dev/null +++ b/shell/platform/windows/flutter_window_winuwp.cc @@ -0,0 +1,337 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/flutter_window_winuwp.h" + +namespace flutter { + +// Multipler used to map controller velocity to an appropriate scroll input. +static constexpr double kControllerScrollMultiplier = 3; + +FlutterWindowWinUWP::FlutterWindowWinUWP( + ABI::Windows::UI::Core::CoreWindow* window) { + winrt::Windows::UI::Core::CoreWindow cw{nullptr}; + winrt::copy_from_abi(cw, window); + window_ = cw; + + SetEventHandlers(); + ConfigureXboxSpecific(); + + current_display_info_ = winrt::Windows::Graphics::Display:: + DisplayInformation::GetForCurrentView(); +} + +WindowsRenderTarget FlutterWindowWinUWP::GetRenderTarget() { +#ifdef USECOREWINDOW + return WindowsRenderTarget(window_); +#else + compositor_ = winrt::Windows::UI::Composition::Compositor(); + target_ = compositor_.CreateTargetForCurrentView(); + visual_tree_root_ = compositor_.CreateContainerVisual(); + target_.Root(visual_tree_root_); + + cursor_visual_ = CreateCursorVisual(); + + render_target_ = compositor_.CreateSpriteVisual(); + if (running_on_xbox_) { + render_target_.Offset( + {xbox_overscan_x_offset_, xbox_overscan_y_offset_, 1.0}); + } else { + render_target_.Offset({1.0, 1.0, 1.0}); + ApplyInverseDpiScalingTransform(); + } + visual_tree_root_.Children().InsertAtBottom(render_target_); + + WindowBoundsWinUWP bounds = GetBounds(current_display_info_, true); + + render_target_.Size({bounds.width, bounds.height}); + return WindowsRenderTarget(render_target_); +#endif +} + +void FlutterWindowWinUWP::ApplyInverseDpiScalingTransform() { + // Apply inverse transform to negate built in DPI scaling in order to render + // at native scale. + auto dpiScale = GetDpiScale(); + render_target_.Scale({1 / dpiScale, 1 / dpiScale, 1 / dpiScale}); +} + +PhysicalWindowBounds FlutterWindowWinUWP::GetPhysicalWindowBounds() { + WindowBoundsWinUWP bounds = GetBounds(current_display_info_, true); + return {static_cast(bounds.width), + static_cast(bounds.height)}; +} + +void FlutterWindowWinUWP::UpdateFlutterCursor(const std::string& cursor_name) { + // TODO(clarkezone): add support for Flutter cursors: + // https://github.com/flutter/flutter/issues/70199 +} + +void FlutterWindowWinUWP::UpdateCursorRect(const Rect& rect) { + // TODO(cbracken): Implement IMM candidate window positioning. +} + +void FlutterWindowWinUWP::OnWindowResized() {} + +float FlutterWindowWinUWP::GetDpiScale() { + auto disp = winrt::Windows::Graphics::Display::DisplayInformation:: + GetForCurrentView(); + + return GetDpiScale(disp); +} + +WindowBoundsWinUWP FlutterWindowWinUWP::GetBounds( + winrt::Windows::Graphics::Display::DisplayInformation const& disp, + bool physical) { + winrt::Windows::UI::ViewManagement::ApplicationView app_view = + winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + winrt::Windows::Foundation::Rect bounds = app_view.VisibleBounds(); + if (running_on_xbox_) { + return {bounds.Width + (bounds.X), bounds.Height + (bounds.Y)}; + } + + if (physical) { + // Return the height in physical pixels + return {bounds.Width * static_cast(disp.RawPixelsPerViewPixel()), + bounds.Height * static_cast(disp.RawPixelsPerViewPixel())}; + } + + return {bounds.Width, bounds.Height}; +} + +float FlutterWindowWinUWP::GetDpiScale( + winrt::Windows::Graphics::Display::DisplayInformation const& disp) { + double raw_per_view = disp.RawPixelsPerViewPixel(); + + // TODO(clarkezone): ensure DPI handling is correct: + // because XBOX has display scaling off, logicalDpi retuns 96 which is + // incorrect check if raw_per_view is more acurate. + // Also confirm if it is necessary to use this workaround on 10X + // https://github.com/flutter/flutter/issues/70198 + + if (running_on_xbox_) { + return 1.5; + } + return static_cast(raw_per_view); +} + +FlutterWindowWinUWP::~FlutterWindowWinUWP() {} + +void FlutterWindowWinUWP::SetView(WindowBindingHandlerDelegate* view) { + binding_handler_delegate_ = view; +} + +void FlutterWindowWinUWP::SetEventHandlers() { + auto app_view = + winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + + app_view.SetDesiredBoundsMode(winrt::Windows::UI::ViewManagement:: + ApplicationViewBoundsMode::UseCoreWindow); + + app_view.VisibleBoundsChanged({this, &FlutterWindowWinUWP::OnBoundsChanged}); + + window_.PointerPressed({this, &FlutterWindowWinUWP::OnPointerPressed}); + window_.PointerReleased({this, &FlutterWindowWinUWP::OnPointerReleased}); + window_.PointerMoved({this, &FlutterWindowWinUWP::OnPointerMoved}); + window_.PointerWheelChanged( + {this, &FlutterWindowWinUWP::OnPointerWheelChanged}); + + // TODO(clarkezone) support mouse leave handling + // https://github.com/flutter/flutter/issues/70199 + + // TODO(clarkezone) support system font changed + // https://github.com/flutter/flutter/issues/70198 + + window_.KeyUp({this, &FlutterWindowWinUWP::OnKeyUp}); + window_.KeyDown({this, &FlutterWindowWinUWP::OnKeyDown}); + window_.CharacterReceived({this, &FlutterWindowWinUWP::OnCharacterReceived}); + + auto display = winrt::Windows::Graphics::Display::DisplayInformation:: + GetForCurrentView(); + display.DpiChanged({this, &FlutterWindowWinUWP::OnDpiChanged}); +} + +void FlutterWindowWinUWP::ConfigureXboxSpecific() { + running_on_xbox_ = + winrt::Windows::System::Profile::AnalyticsInfo::VersionInfo() + .DeviceFamily() == L"Windows.Xbox"; + + if (running_on_xbox_) { + bool result = + winrt::Windows::UI::ViewManagement::ApplicationView::GetForCurrentView() + .SetDesiredBoundsMode(winrt::Windows::UI::ViewManagement:: + ApplicationViewBoundsMode::UseCoreWindow); + if (!result) { + OutputDebugString(L"Couldn't set bounds mode."); + } + + winrt::Windows::UI::ViewManagement::ApplicationView app_view = winrt:: + Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + winrt::Windows::Foundation::Rect bounds = app_view.VisibleBounds(); + + // the offset /2 represents how much off-screan the core window is + // positioned unclear why disabling overscan doesn't correct this + xbox_overscan_x_offset_ = bounds.X / 2; + xbox_overscan_y_offset_ = bounds.Y / 2; + } +} + +void FlutterWindowWinUWP::OnDpiChanged( + winrt::Windows::Graphics::Display::DisplayInformation const& args, + winrt::Windows::Foundation::IInspectable const&) { + ApplyInverseDpiScalingTransform(); + + WindowBoundsWinUWP bounds = GetBounds(current_display_info_, true); + + binding_handler_delegate_->OnWindowSizeChanged( + static_cast(bounds.width), static_cast(bounds.height)); +} + +void FlutterWindowWinUWP::OnPointerPressed( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args) { + double x = GetPosX(args); + double y = GetPosY(args); + + binding_handler_delegate_->OnPointerDown( + x, y, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); +} + +void FlutterWindowWinUWP::OnPointerReleased( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args) { + double x = GetPosX(args); + double y = GetPosY(args); + + binding_handler_delegate_->OnPointerUp( + x, y, FlutterPointerMouseButtons::kFlutterPointerButtonMousePrimary); +} + +void FlutterWindowWinUWP::OnPointerMoved( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args) { + double x = GetPosX(args); + double y = GetPosY(args); + + binding_handler_delegate_->OnPointerMove(x, y); +} + +void FlutterWindowWinUWP::OnPointerWheelChanged( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args) { + double x = GetPosX(args); + double y = GetPosY(args); + int delta = args.CurrentPoint().Properties().MouseWheelDelta(); + binding_handler_delegate_->OnScroll(x, y, 0, -delta, 1); +} + +double FlutterWindowWinUWP::GetPosX( + winrt::Windows::UI::Core::PointerEventArgs const& args) { + const double inverse_dpi_scale = GetDpiScale(); + + return (args.CurrentPoint().Position().X - xbox_overscan_x_offset_) * + inverse_dpi_scale; +} + +double FlutterWindowWinUWP::GetPosY( + winrt::Windows::UI::Core::PointerEventArgs const& args) { + const double inverse_dpi_scale = GetDpiScale(); + return static_cast( + (args.CurrentPoint().Position().Y - xbox_overscan_y_offset_) * + inverse_dpi_scale); +} + +void FlutterWindowWinUWP::OnBoundsChanged( + winrt::Windows::UI::ViewManagement::ApplicationView const& app_view, + winrt::Windows::Foundation::IInspectable const&) { +#ifndef USECOREWINDOW + if (binding_handler_delegate_) { + auto bounds = GetBounds(current_display_info_, true); + + binding_handler_delegate_->OnWindowSizeChanged( + static_cast(bounds.width), static_cast(bounds.height)); + render_target_.Size({bounds.width, bounds.height}); + } +#endif +} + +void FlutterWindowWinUWP::OnKeyUp( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::KeyEventArgs const& args) { + // TODO(clarkezone) complete keyboard handling including + // system key (back), unicode handling, shortcut support, + // handling defered delivery, remove the need for action value. + // https://github.com/flutter/flutter/issues/70202 + auto status = args.KeyStatus(); + unsigned int scancode = status.ScanCode; + int key = static_cast(args.VirtualKey()); + char32_t chararacter = static_cast(key | 32); + int action = 0x0101; + binding_handler_delegate_->OnKey(key, scancode, action, chararacter, false); +} + +void FlutterWindowWinUWP::OnKeyDown( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::KeyEventArgs const& args) { + // TODO(clarkezone) complete keyboard handling including + // system key (back), unicode handling, shortcut support + // handling defered delivery, remove the need for action value. + // https://github.com/flutter/flutter/issues/70202 + auto status = args.KeyStatus(); + unsigned int scancode = status.ScanCode; + int key = static_cast(args.VirtualKey()); + char32_t chararacter = static_cast(key | 32); + int action = 0x0100; + binding_handler_delegate_->OnKey(key, scancode, action, chararacter, false); +} + +void FlutterWindowWinUWP::OnCharacterReceived( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args) { + auto key = args.KeyCode(); + wchar_t keycode = static_cast(key); + if (keycode >= u' ') { + std::u16string text({keycode}); + binding_handler_delegate_->OnText(text); + } +} + +winrt::Windows::UI::Composition::Visual +FlutterWindowWinUWP::CreateCursorVisual() { + auto container = compositor_.CreateContainerVisual(); + container.Offset( + {window_.Bounds().Width / 2, window_.Bounds().Height / 2, 1.0}); + + // size of the simulated mouse cursor + const float size = 30; + auto cursor_visual = compositor_.CreateShapeVisual(); + cursor_visual.Size({size, size}); + + // compensate for overscan in cursor visual + cursor_visual.Offset({xbox_overscan_x_offset_, xbox_overscan_y_offset_, 1.0}); + + winrt::Windows::UI::Composition::CompositionEllipseGeometry circle = + compositor_.CreateEllipseGeometry(); + circle.Radius({size / 2, size / 2}); + + auto circleshape = compositor_.CreateSpriteShape(circle); + circleshape.FillBrush( + compositor_.CreateColorBrush(winrt::Windows::UI::Colors::Black())); + circleshape.Offset({size / 2, size / 2}); + + cursor_visual.Shapes().Append(circleshape); + + winrt::Windows::UI::Composition::Visual visual = + cursor_visual.as(); + + visual.CompositeMode(winrt::Windows::UI::Composition:: + CompositionCompositeMode::DestinationInvert); + + visual.AnchorPoint({0.5, 0.5}); + container.Children().InsertAtTop(visual); + + return container; +} + +} // namespace flutter diff --git a/shell/platform/windows/flutter_window_winuwp.h b/shell/platform/windows/flutter_window_winuwp.h new file mode 100644 index 0000000000000000000000000000000000000000..bc7218de6d3a4bff9c2fc0b013eb7d60b424d52d --- /dev/null +++ b/shell/platform/windows/flutter_window_winuwp.h @@ -0,0 +1,180 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_UWP_FLUTTER_WINDOW_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_UWP_FLUTTER_WINDOW_H_ + +#include +#include +#include +#include + +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" + +namespace flutter { + +struct WindowBoundsWinUWP { + float width; + float height; +}; + +// Implements a UWP CoreWindow. Underlying window has been created and provided +// by the runner. +// +// Specifically handles window events within windows. +class FlutterWindowWinUWP : public WindowBindingHandler { + public: + explicit FlutterWindowWinUWP(ABI::Windows::UI::Core::CoreWindow* window); + + virtual ~FlutterWindowWinUWP(); + + // |WindowBindingHandler| + void SetView(WindowBindingHandlerDelegate* view) override; + + // |WindowBindingHandler| + WindowsRenderTarget GetRenderTarget() override; + + // |WindowBindingHandler| + float GetDpiScale() override; + + // |WindowBindingHandler| + PhysicalWindowBounds GetPhysicalWindowBounds() override; + + // |WindowBindingHandler| + void UpdateFlutterCursor(const std::string& cursor_name) override; + + // |WindowBindingHandler| + void UpdateCursorRect(const Rect& rect) override; + + // |WindowBindingHandler| + void OnWindowResized() override; + + private: + // Returns a bounds structure containing width and height information + // for the backing CoreWindow in either view or physical pixels depending on + // |physical|. + WindowBoundsWinUWP GetBounds( + winrt::Windows::Graphics::Display::DisplayInformation const& disp, + bool physical); + + // Returns a scaling factor representing the current DPI scale applied to the + // backing CoreWindow. + float GetDpiScale( + winrt::Windows::Graphics::Display::DisplayInformation const&); + + // Undoes the scale transform applied by the Windows compositor in order to + // render at native scale and produce smooth results on high DPI screens. + void ApplyInverseDpiScalingTransform(); + + // Hooks up event handers for keyboard, mouse, size, DPI changed events on the + // underlying CoreWindow. + void SetEventHandlers(); + + // Notifies current |WindowBindingHandlerDelegate| of DPI Changed events. + void OnDpiChanged( + winrt::Windows::Graphics::Display::DisplayInformation const& args, + winrt::Windows::Foundation::IInspectable const&); + + // Notifies current |WindowBindingHandlerDelegate| of pointer pressed events. + void OnPointerPressed(winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of pointer released events. + void OnPointerReleased( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of pointer pressed events. + void OnBoundsChanged( + winrt::Windows::UI::ViewManagement::ApplicationView const& appView, + winrt::Windows::Foundation::IInspectable const&); + + // Notifies current |WindowBindingHandlerDelegate| of pointer moved events. + void OnPointerMoved(winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of mouse wheel events. + void OnPointerWheelChanged( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of key up events. + void OnKeyUp(winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::KeyEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of key down events. + void OnKeyDown(winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::KeyEventArgs const& args); + + // Notifies current |WindowBindingHandlerDelegate| of character received + // events. + void OnCharacterReceived( + winrt::Windows::Foundation::IInspectable const&, + winrt::Windows::UI::Core::CharacterReceivedEventArgs const& args); + + // Creates a visual representing the emulated cursor and add to the visual + // tree + winrt::Windows::UI::Composition::Visual CreateCursorVisual(); + + // Test is current context is running on an xbox device and perform device + // specific initialization. + void ConfigureXboxSpecific(); + + // Converts from logical point to physical X value. + double GetPosX(winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Converts from logical point to physical Y value. + double GetPosY(winrt::Windows::UI::Core::PointerEventArgs const& args); + + // Token representing current worker thread. + winrt::Windows::Foundation::IAsyncAction worker_loop_{nullptr}; + + // Backing CoreWindow. nullptr if not set. + winrt::Windows::UI::Core::CoreWindow window_{nullptr}; + + // Pointer to a FlutterWindowsView that can be + // used to update engine windowing and input state. + WindowBindingHandlerDelegate* binding_handler_delegate_; + + // Current active compositor. nullptr if not set. + winrt::Windows::UI::Composition::Compositor compositor_{nullptr}; + + // Current CompositionTarget for binding the + // rendering context to the CoreWindow. nullptr if not set. + winrt::Windows::UI::Composition::CompositionTarget target_{nullptr}; + + // Composition tree root object. + winrt::Windows::UI::Composition::ContainerVisual visual_tree_root_{nullptr}; + + // Composition visual representing the emulated + // cursor visual. + winrt::Windows::UI::Composition::Visual cursor_visual_{nullptr}; + + // Compositor object that represents the render target binding the backing + // SwapChain to the CoreWindow. + winrt::Windows::UI::Composition::SpriteVisual render_target_{nullptr}; + + // Is current context is executing on an XBOX + // device. + bool running_on_xbox_ = false; + + // Current X overscan compensation factor. + float xbox_overscan_x_offset_ = 0.0f; + + // Current Y overscan compensation factor. + float xbox_overscan_y_offset_ = 0.0f; + + // Most recent display information. + winrt::Windows::Graphics::Display::DisplayInformation current_display_info_{ + nullptr}; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_UWP_FLUTTER_WINDOW_H_ diff --git a/shell/platform/windows/flutter_windows_winuwp.cc b/shell/platform/windows/flutter_windows_winuwp.cc index 35cd27e25f6615d4765967bb02809ea7ac282dc6..3b235f22ef344af1c23a6f24d633eb5f3ce33dad 100644 --- a/shell/platform/windows/flutter_windows_winuwp.cc +++ b/shell/platform/windows/flutter_windows_winuwp.cc @@ -17,29 +17,36 @@ #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" +#include "flutter/shell/platform/windows/flutter_window_winuwp.h" // nogncheck -FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( - int width, - int height, - FlutterDesktopEngineRef engine) { - // TODO add WINUWP implementation. - return nullptr; -} - -uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) { - // TODO add WINUWP implementation. - return 0; +// Returns the engine corresponding to the given opaque API handle. +static flutter::FlutterWindowsEngine* EngineFromHandle( + FlutterDesktopEngineRef ref) { + return reinterpret_cast(ref); } -void FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( - FlutterDesktopPluginRegistrarRef registrar, - FlutterDesktopWindowProcCallback delegate, - void* user_data) { - // TODO add WINUWP implementation. -} - -void FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( - FlutterDesktopPluginRegistrarRef registrar, - FlutterDesktopWindowProcCallback delegate) { - // TODO add WINUWP implementation. +FlutterDesktopViewControllerRef +FlutterDesktopViewControllerCreateFromCoreWindow( + ABI::Windows::UI::Core::CoreWindow* window, + FlutterDesktopEngineRef engine) { + std::unique_ptr window_wrapper = + std::make_unique(window); + + auto state = std::make_unique(); + state->view = + std::make_unique(std::move(window_wrapper)); + state->view->CreateRenderSurface(); + + // Take ownership of the engine, starting it if necessary. + state->view->SetEngine( + std::unique_ptr(EngineFromHandle(engine))); + if (!state->view->GetEngine()->running()) { + if (!state->view->GetEngine()->RunWithEntrypoint(nullptr)) { + return nullptr; + } + } + + // Must happen after engine is running. + state->view->SendInitialBounds(); + return state.release(); } diff --git a/shell/platform/windows/key_event_handler.cc b/shell/platform/windows/key_event_handler.cc index 67942e1e12edbda99c26d3f924dd60600bbd2ef4..a6d2e512f36bc3c8859a2504d65e7a457c09ad81 100644 --- a/shell/platform/windows/key_event_handler.cc +++ b/shell/platform/windows/key_event_handler.cc @@ -51,6 +51,9 @@ static constexpr int kCapsLock = 1 << 11; static constexpr int kNumLock = 1 << 12; static constexpr int kScrollLock = 1 << 13; +// TODO(clarkezone) need to add support for get keystate +// https://github.com/flutter/flutter/issues/70202 +#ifndef WINUWP /// Calls GetKeyState() an all modifier keys and packs the result in an int, /// with the re-defined values declared above for compatibility with the Flutter /// framework. @@ -87,6 +90,7 @@ int GetModsForKeyState() { mods |= kScrollLock; return mods; } +#endif // This uses event data instead of generating a serial number because // information can't be attached to the redispatched events, so it has to be @@ -219,7 +223,9 @@ bool KeyEventHandler::KeyboardHook(FlutterWindowsView* view, event.AddMember(kScanCodeKey, scancode, allocator); event.AddMember(kCharacterCodePointKey, character, allocator); event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator); +#ifndef WINUWP event.AddMember(kModifiersKey, GetModsForKeyState(), allocator); +#endif switch (action) { case WM_KEYDOWN: diff --git a/shell/platform/windows/public/flutter_windows.h b/shell/platform/windows/public/flutter_windows.h index a79c357bdbeed837d9dd18ed547d6846b63545c4..e1efbef34e7a7138811ff5218dda48ef6df1ee13 100644 --- a/shell/platform/windows/public/flutter_windows.h +++ b/shell/platform/windows/public/flutter_windows.h @@ -13,6 +13,10 @@ #include "flutter_messenger.h" #include "flutter_plugin_registrar.h" +#ifdef WINUWP +#include +#endif + #if defined(__cplusplus) extern "C" { #endif @@ -70,10 +74,21 @@ typedef struct { // The caller owns the returned reference, and is responsible for calling // FlutterDesktopViewControllerDestroy. Returns a null pointer in the event of // an error. +#ifdef WINUWP +// The CoreWindow implementation accepts a pointer to the host CoreWindow +// and view hookup is performed in the construction path. +FLUTTER_EXPORT FlutterDesktopViewControllerRef +FlutterDesktopViewControllerCreateFromCoreWindow( + ABI::Windows::UI::Core::CoreWindow* window, + FlutterDesktopEngineRef engine); +#else //! WINUWP +// The Win32 implementation accepts width, height +// with view hookup explicitly performed using the caller using HWND parenting. FLUTTER_EXPORT FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(int width, int height, FlutterDesktopEngineRef engine); +#endif // Shuts down the engine instance associated with |controller|, and cleans up // associated state. @@ -166,8 +181,10 @@ FlutterDesktopEngineGetTextureRegistrar( // ========== View ========== +#ifndef WINUWP // Return backing HWND for manipulation in host application. FLUTTER_EXPORT HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view); +#endif // ========== Plugin Registrar (extensions) ========== // These are Windows-specific extensions to flutter_plugin_registrar.h @@ -191,6 +208,7 @@ typedef bool (*FlutterDesktopWindowProcCallback)(HWND /* hwnd */, FLUTTER_EXPORT FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView( FlutterDesktopPluginRegistrarRef registrar); +#ifndef WINUWP FLUTTER_EXPORT void FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( FlutterDesktopPluginRegistrarRef registrar, @@ -201,6 +219,7 @@ FLUTTER_EXPORT void FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( FlutterDesktopPluginRegistrarRef registrar, FlutterDesktopWindowProcCallback delegate); +#endif // ========== Freestanding Utilities ========== diff --git a/shell/platform/windows/system_utils_winuwp.cc b/shell/platform/windows/system_utils_winuwp.cc index 85278dc0a6dd5b11c200fe375dd31edc30fdfb80..a37203a0e499e47f9486b08c2db5750e111619a6 100644 --- a/shell/platform/windows/system_utils_winuwp.cc +++ b/shell/platform/windows/system_utils_winuwp.cc @@ -15,20 +15,50 @@ namespace flutter { std::vector GetPreferredLanguageInfo() { std::vector languages = GetPreferredLanguages(); std::vector language_info; - // TODO populate via WinRT + for (auto language : languages) { + language_info.push_back(ParseLanguageName(language)); + } return language_info; } std::vector GetPreferredLanguages() { std::vector languages; - // TODO populate via WinRT + // TODO(clarkezone) need to implement a complete version of this function in + // order to get full list of platform languages + // https://github.com/flutter/flutter/issues/74156 + languages.push_back(L"en-US"); + languages.push_back(L"en"); + return languages; } LanguageInfo ParseLanguageName(std::wstring language_name) { LanguageInfo info; - // TODO populate via WinRT + // Split by '-', discarding any suplemental language info (-x-foo). + std::vector components; + std::istringstream stream(Utf8FromUtf16(language_name)); + std::string component; + while (getline(stream, component, '-')) { + if (component == "x") { + break; + } + components.push_back(component); + } + + // Determine which components are which. + info.language = components[0]; + if (components.size() == 3) { + info.script = components[1]; + info.region = components[2]; + } else if (components.size() == 2) { + // A script code will always be four characters long. + if (components[1].size() == 4) { + info.script = components[1]; + } else { + info.region = components[1]; + } + } return info; } diff --git a/shell/platform/windows/task_runner_winuwp.cc b/shell/platform/windows/task_runner_winuwp.cc index 8bfc4c9cbd2cc05b9857cd2b38672f51054f20eb..d6c5c6bc70d93fa555750bb98667aaf018d48442 100644 --- a/shell/platform/windows/task_runner_winuwp.cc +++ b/shell/platform/windows/task_runner_winuwp.cc @@ -45,7 +45,7 @@ void TaskRunnerWinUwp::PostTask(TaskClosure task) { // TODO: Handle the target time. See PostFlutterTask() dispatcher_.RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, - [task]() { task() }); + [task]() { task(); }); } } // namespace flutter diff --git a/shell/platform/windows/win32_flutter_window_unittests.cc b/shell/platform/windows/win32_flutter_window_unittests.cc index c53843e9a437ecfeccf80ec047154887962c9c04..dadaad0ef1f53576c780d9d434288320f28a6b72 100644 --- a/shell/platform/windows/win32_flutter_window_unittests.cc +++ b/shell/platform/windows/win32_flutter_window_unittests.cc @@ -71,7 +71,8 @@ class SpyKeyEventHandler : public KeyboardHookHandler { void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); MOCK_METHOD0(ComposeEndHook, void()); - MOCK_METHOD2(ComposeChangeHook, void(const std::u16string& text, int cursor_pos)); + MOCK_METHOD2(ComposeChangeHook, + void(const std::u16string& text, int cursor_pos)); private: std::unique_ptr real_implementation_; @@ -103,7 +104,8 @@ class SpyTextInputPlugin : public KeyboardHookHandler, void(FlutterWindowsView* window, const std::u16string& text)); MOCK_METHOD0(ComposeBeginHook, void()); MOCK_METHOD0(ComposeEndHook, void()); - MOCK_METHOD2(ComposeChangeHook, void(const std::u16string& text, int cursor_pos)); + MOCK_METHOD2(ComposeChangeHook, + void(const std::u16string& text, int cursor_pos)); virtual void OnCursorRectUpdated(const Rect& rect) {} diff --git a/shell/platform/windows/window_binding_handler.h b/shell/platform/windows/window_binding_handler.h index c8ef56e601b57b163a971aa8d6eeb7f099db1010..9263cc5b2634b43b3a662bc6b8d0fc5d92098e9e 100644 --- a/shell/platform/windows/window_binding_handler.h +++ b/shell/platform/windows/window_binding_handler.h @@ -14,6 +14,10 @@ #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" +#ifdef WINUWP +#include +#endif + namespace flutter { class FlutterWindowsView; @@ -24,8 +28,14 @@ struct PhysicalWindowBounds { size_t height; }; -using WindowsRenderTarget = std::variant< - /*winrt::Windows::UI::Composition::SpriteVisual, */ HWND>; +#ifdef WINUWP +using WindowsRenderTarget = + std::variant; +#else +using WindowsRenderTarget = std::variant; +#endif // Abstract class for binding Windows platform windows to Flutter views. class WindowBindingHandler { diff --git a/shell/platform/windows/window_binding_handler_delegate.h b/shell/platform/windows/window_binding_handler_delegate.h index 8b8b1acc7669e34dae9131144382171dcac89d63..dd0038f0fc19467fcd1f0cb7d26984000f71dd9b 100644 --- a/shell/platform/windows/window_binding_handler_delegate.h +++ b/shell/platform/windows/window_binding_handler_delegate.h @@ -41,9 +41,11 @@ class WindowBindingHandlerDelegate { // Typically called by currently configured WindowBindingHandler virtual void OnText(const std::u16string&) = 0; - // Notifies delegate that backing window size has received key press. Should - // return true if the event was handled and should not be propagated. - // Typically called by currently configured WindowBindingHandler. + // TODO(clarkezone) refactor delegate to avoid needing win32 magic values in + // UWP implementation https://github.com/flutter/flutter/issues/70202 Notifies + // delegate that backing window size has received key press. Should return + // true if the event was handled and should not be propagated. Typically + // called by currently configured WindowBindingHandler. virtual bool OnKey(int key, int scancode, int action,