diff --git a/change/react-native-windows-2b4a6ca8-3a50-4be5-8b1d-559946fa0be1.json b/change/react-native-windows-2b4a6ca8-3a50-4be5-8b1d-559946fa0be1.json new file mode 100644 index 00000000000..2e53ffae7a8 --- /dev/null +++ b/change/react-native-windows-2b4a6ca8-3a50-4be5-8b1d-559946fa0be1.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[0.76] Cleanup ReactNativeAppBuilder and ReactNativeWin32App", + "packageName": "react-native-windows", + "email": "jthysell@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp index d85529dd80b..2aeaae1397e 100644 --- a/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp +++ b/packages/sample-app-fabric/windows/SampleAppFabric/SampleAppFabric.cpp @@ -8,6 +8,7 @@ #include "NativeModules.h" +// A PackageProvider containing any turbo modules you define within this app project struct CompReactPackageProvider : winrt::implements { public: // IReactPackageProvider @@ -16,65 +17,66 @@ struct CompReactPackageProvider } }; -// Global Variables: -constexpr PCWSTR windowTitle = L"sample_app_fabric"; -constexpr PCWSTR mainComponentName = L"sample_app_fabric"; - +// The entry point of the Win32 application _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR /* commandLine */, int showCmd) { - // Initialize WinRT. + // Initialize WinRT winrt::init_apartment(winrt::apartment_type::single_threaded); // Enable per monitor DPI scaling SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - // Create a top-level window. - auto window = winrt::Microsoft::UI::Windowing::AppWindow::Create(); - window.Title(windowTitle); - window.Resize({1000, 1000}); - window.Show(); - + // Find the path hosting the app exe file WCHAR appDirectory[MAX_PATH]; GetModuleFileNameW(NULL, appDirectory, MAX_PATH); PathCchRemoveFileSpec(appDirectory, MAX_PATH); - // DebugBundlePath is used when loading from a bundle server such as metro. If this parameter - // is not provided fallback to a combination of JavaScriptBundleFile and BundleRootPath - auto reactInstanceSettingsBuilder { - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder() - .DebugBundlePath(L"index") - .JavaScriptBundleFile(L"index.windows") - .BundleRootPath(appDirectory) + // Create a ReactNativeWin32App with the ReactNativeAppBuilder + auto reactNativeWin32App{winrt::Microsoft::ReactNative::ReactNativeAppBuilder().Build()}; + + // Configure the initial InstanceSettings for the app's ReactNativeHost + auto settings{reactNativeWin32App.ReactNativeHost().InstanceSettings()}; + // Register any autolinked native modules + RegisterAutolinkedNativeModulePackages(settings.PackageProviders()); + // Register any native modules defined within this app project + settings.PackageProviders().Append(winrt::make()); + #if BUNDLE - .UseFastRefresh(false) + // Load the JS bundle from a file (not Metro): + // Set the path (on disk) where the .bundle file is located + settings.BundleRootPath(std::wstring(L"file://").append(appDirectory).append(L"\\Bundle\\").c_str()); + // Set the name of the bundle file (without the .bundle extension) + settings.JavaScriptBundleFile(L"index.windows"); + // Disable hot reload + settings.UseFastRefresh(false); #else - .UseFastRefresh(true) + // Load the JS bundle from Metro + settings.JavaScriptBundleFile(L"index"); + // Enable hot reload + settings.UseFastRefresh(true); #endif #if _DEBUG - .UseDirectDebugger(true) - .UseDeveloperSupport(true) + // For Debug builds + // Enable Direct Debugging of JS + settings.UseDirectDebugger(true); + // Enable the Developer Menu + settings.UseDeveloperSupport(true); #else - .UseDirectDebugger(false) - .UseDeveloperSupport(false) + // For Release builds: + // Disable Direct Debugging of JS + settings.UseDirectDebugger(false); + // Disable the Developer Menu + settings.UseDeveloperSupport(false); #endif - }; - - auto packageProviders{winrt::single_threaded_vector()}; - - RegisterAutolinkedNativeModulePackages(packageProviders); - packageProviders.Append(winrt::make()); - - winrt::Microsoft::ReactNative::ReactViewOptions viewOptions; - viewOptions.ComponentName(mainComponentName); - // Initialize and Manage the ReactNativeHost - auto reactNativeAppBuilder{winrt::Microsoft::ReactNative::ReactNativeAppBuilder() - .AddPackageProviders(packageProviders) - .SetReactInstanceSettings(reactInstanceSettingsBuilder.ReactInstanceSettings()) - .SetAppWindow(window) - .SetReactViewOptions(viewOptions)}; + // Get the AppWindow so we can configure its initial title and size + auto appWindow{reactNativeWin32App.AppWindow()}; + appWindow.Title(L"sample_app_fabric"); + appWindow.Resize({1000, 1000}); - // Start the react-native instance by creating a javascript runtime and load the bundle. - auto reactNativeWin32App{reactNativeAppBuilder.Build()}; + // Get the ReactViewOptions so we can set the initial RN component to load + auto viewOptions{reactNativeWin32App.ReactViewOptions()}; + viewOptions.ComponentName(L"sample_app_fabric"); + // Start the app reactNativeWin32App.Start(); } diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp b/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp deleted file mode 100644 index 5f847101095..00000000000 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "pch.h" -#include "ReactInstanceSettingsBuilder.h" -#include "ReactInstanceSettingsBuilder.g.cpp" -#include "ReactInstanceSettings.h" - -namespace winrt::ReactNative { -using namespace winrt::Microsoft::ReactNative; -} - -namespace winrt::UI { -using namespace winrt::Microsoft::UI; -} - -namespace winrt::Microsoft::ReactNative::implementation { -ReactInstanceSettingsBuilder::ReactInstanceSettingsBuilder() { - m_reactInstanceSettings = winrt::make(); -} - -winrt::ReactNative::ReactInstanceSettings ReactInstanceSettingsBuilder::ReactInstanceSettings() { - return m_reactInstanceSettings; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::UseDirectDebugger(bool const &state) { - m_reactInstanceSettings.UseDirectDebugger(state); - - return *this; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::UseDeveloperSupport(bool const &state) { - m_reactInstanceSettings.UseDeveloperSupport(state); - - return *this; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::BundleRootPath(hstring const &path) { - m_reactInstanceSettings.BundleRootPath(std::wstring(L"file://").append(path.c_str()).append(L"\\Bundle\\").c_str()); - - return *this; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::DebugBundlePath(hstring const &path) { - m_reactInstanceSettings.DebugBundlePath(path.c_str()); - - return *this; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::JavaScriptBundleFile( - hstring const &file) { - m_reactInstanceSettings.JavaScriptBundleFile(file.c_str()); - - return *this; -} - -winrt::ReactNative::ReactInstanceSettingsBuilder ReactInstanceSettingsBuilder::UseFastRefresh(bool const &state) { - m_reactInstanceSettings.UseFastRefresh(state); - - return *this; -} -} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h b/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h deleted file mode 100644 index 7cd712a9297..00000000000 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include "ReactInstanceSettingsBuilder.g.h" - -namespace winrt::Microsoft::ReactNative::implementation { -struct ReactInstanceSettingsBuilder : ReactInstanceSettingsBuilderT { - ReactInstanceSettingsBuilder(); - - winrt::Microsoft::ReactNative::ReactInstanceSettings ReactInstanceSettings(); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder UseDirectDebugger(bool const &state); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder UseDeveloperSupport(bool const &state); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder BundleRootPath(hstring const &path); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder DebugBundlePath(hstring const &path); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder JavaScriptBundleFile(hstring const &file); - winrt::Microsoft::ReactNative::ReactInstanceSettingsBuilder UseFastRefresh(bool const &state); - - private: - winrt::Microsoft::ReactNative::ReactInstanceSettings m_reactInstanceSettings{nullptr}; -}; -} // namespace winrt::Microsoft::ReactNative::implementation -namespace winrt::Microsoft::ReactNative::factory_implementation { -struct ReactInstanceSettingsBuilder - : ReactInstanceSettingsBuilderT {}; -} // namespace winrt::Microsoft::ReactNative::factory_implementation diff --git a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.cpp b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.cpp index 80f568e152f..743ffa34c4e 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +++ b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.cpp @@ -1,43 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #include "pch.h" #include "ReactNativeAppBuilder.h" #include "ReactNativeAppBuilder.g.cpp" -#include "IReactDispatcher.h" -#include "ReactNativeHost.h" + #include "ReactNativeWin32App.h" + +#include "winrt/Microsoft.ReactNative.h" #include "winrt/Microsoft.UI.Composition.h" #include "winrt/Microsoft.UI.Dispatching.h" +#include "winrt/Microsoft.UI.Interop.h" #include "winrt/Microsoft.UI.Windowing.h" -#include "winrt/microsoft.UI.Interop.h" - -// Scaling factor for the window's content based on the DPI of the display where the window is located. -float ScaleFactor(HWND hwnd) noexcept { - return GetDpiForWindow(hwnd) / static_cast(USER_DEFAULT_SCREEN_DPI); -} - -void UpdateRootViewSizeToAppWindow( - winrt::Microsoft::ReactNative::ReactNativeIsland const &rootView, - winrt::Microsoft::UI::Windowing::AppWindow const &window) { - auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); - auto scaleFactor = ScaleFactor(hwnd); - winrt::Windows::Foundation::Size size{ - window.ClientSize().Width / scaleFactor, window.ClientSize().Height / scaleFactor}; - // Do not relayout when minimized - if (window.Presenter().as().State() != - winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { - winrt::Microsoft::ReactNative::LayoutConstraints constraints; - constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined; - constraints.MaximumSize = constraints.MinimumSize = size; - rootView.Arrange(constraints, {0, 0}); - } -} - -namespace winrt::ReactNative { -using namespace winrt::Microsoft::ReactNative; -} - -namespace winrt::UI { -using namespace winrt::Microsoft::UI; -} namespace winrt::Microsoft::ReactNative::implementation { ReactNativeAppBuilder::ReactNativeAppBuilder() { @@ -46,133 +20,55 @@ ReactNativeAppBuilder::ReactNativeAppBuilder() { ReactNativeAppBuilder::~ReactNativeAppBuilder() {} -winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::AddPackageProviders( - winrt::Windows::Foundation::Collections::IVector const - &packageProviders) { - for (auto const &provider : packageProviders) { - m_reactNativeWin32App.ReactNativeHost().PackageProviders().Append(provider); - } - - return *this; -} - -winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetReactInstanceSettings( - winrt::Microsoft::ReactNative::ReactInstanceSettings const &settings) { - m_reactNativeWin32App.ReactNativeHost().InstanceSettings(settings); - +winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetDispatcherQueueController( + winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController) { + m_reactNativeWin32App.as().get()->DispatcherQueueController( + dispatcherQueueController); return *this; } -winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetCompositor( +winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetCompositor( winrt::Microsoft::UI::Composition::Compositor const &compositor) { m_reactNativeWin32App.as().get()->Compositor(compositor); return *this; } -winrt::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetAppWindow( +winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetAppWindow( winrt::Microsoft::UI::Windowing::AppWindow const &appWindow) { m_reactNativeWin32App.as().get()->AppWindow(appWindow); return *this; } -winrt::Microsoft::ReactNative::ReactNativeAppBuilder ReactNativeAppBuilder::SetReactViewOptions( - winrt::Microsoft::ReactNative::ReactViewOptions const &reactViewOptions) { - m_reactViewOptions = reactViewOptions; +winrt::Microsoft::ReactNative::ReactNativeWin32App ReactNativeAppBuilder::Build() { + // Create the DispatcherQueueController if the app developer doesn't provide one + if (m_reactNativeWin32App.as().get()->DispatcherQueueController() == nullptr) { + assert(m_reactNativeWin32App.as().get()->Compositor() == nullptr); - return *this; -} - -winrt::ReactNative::ReactNativeWin32App ReactNativeAppBuilder::Build() { - if (m_reactNativeWin32App.Compositor() == nullptr) { - // Create a DispatcherQueue for this thread. This is needed for Composition, Content, and - // Input APIs. + // Create a DispatcherQueue for this thread. This is needed for Composition, Content, and Input APIs. auto dispatcherQueueController = winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread(); - m_reactNativeWin32App.as().get()->DispatchQueueController( + m_reactNativeWin32App.as().get()->DispatcherQueueController( dispatcherQueueController); + } - // Create the compositor on behalf of the App Developer + // Create the Compositor if the app developer doesn't provide one + if (m_reactNativeWin32App.as().get()->Compositor() == nullptr) { + // Create the compositor on behalf of the App Developer. auto compositor = winrt::Microsoft::UI::Composition::Compositor(); m_reactNativeWin32App.as().get()->Compositor(compositor); } - // Create the AppWindow if the developer doesn't provide one + // Create the AppWindow if the app developer doesn't provide one if (m_reactNativeWin32App.AppWindow() == nullptr) { auto appWindow = winrt::Microsoft::UI::Windowing::AppWindow::Create(); - appWindow.Title(L"SampleApplication"); + appWindow.Title(L"ReactNativeWin32App"); appWindow.Resize({1000, 1000}); - appWindow.Show(); m_reactNativeWin32App.as().get()->AppWindow(appWindow); } - // Currently set the property to use current thread dispatcher as a default UI dispatcher. - // TODO: Provision for setting dispatcher based on the thread dispatcherQueueController is created. - m_reactNativeWin32App.ReactNativeHost().InstanceSettings().Properties().Set( - ReactDispatcherHelper::UIDispatcherProperty(), ReactDispatcherHelper::UIThreadDispatcher()); - - auto hwnd{winrt::UI::GetWindowFromWindowId(m_reactNativeWin32App.AppWindow().Id())}; - - winrt::ReactNative::ReactCoreInjection::SetTopLevelWindowId( - m_reactNativeWin32App.ReactNativeHost().InstanceSettings().Properties(), reinterpret_cast(hwnd)); - - winrt::ReactNative::Composition::CompositionUIService::SetCompositor( - m_reactNativeWin32App.ReactNativeHost().InstanceSettings(), m_reactNativeWin32App.Compositor()); - - // Start the react-native instance, which will create a JavaScript runtime and load the applications bundle. - m_reactNativeWin32App.ReactNativeHost().ReloadInstance(); - - // Create a RootView which will present a react-native component - auto reactNativeIsland = winrt::Microsoft::ReactNative::ReactNativeIsland(m_reactNativeWin32App.Compositor()); - reactNativeIsland.ReactViewHost(winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost( - m_reactNativeWin32App.ReactNativeHost(), m_reactViewOptions)); - - m_reactNativeWin32App.as().get()->ReactNativeIsland( - std::move(reactNativeIsland)); - - // Update the size of the RootView when the AppWindow changes size - m_reactNativeWin32App.AppWindow().Changed( - [wkRootView = winrt::make_weak(m_reactNativeWin32App.ReactNativeIsland())]( - winrt::Microsoft::UI::Windowing::AppWindow const &window, - winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs const &args) { - if (args.DidSizeChange() || args.DidVisibilityChange()) { - if (auto rootView = wkRootView.get()) { - UpdateRootViewSizeToAppWindow(rootView, window); - } - } - }); - - // Quit application when main window is closed - m_reactNativeWin32App.AppWindow().Destroying([this]( - winrt::Microsoft::UI::Windowing::AppWindow const &window, - winrt::Windows::Foundation::IInspectable const & /*args*/) { - // Before we shutdown the application - unload the ReactNativeHost to give the javascript a chance to save any - // state - auto async = m_reactNativeWin32App.ReactNativeHost().UnloadInstance(); - async.Completed([this](auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) { - assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); - m_reactNativeWin32App.ReactNativeHost().InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); }); - }); - }); - - // DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland - auto desktopChildSiteBridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( - m_reactNativeWin32App.Compositor(), m_reactNativeWin32App.AppWindow().Id()); - - desktopChildSiteBridge.Connect(m_reactNativeWin32App.ReactNativeIsland().Island()); - - desktopChildSiteBridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); - - auto scaleFactor = ScaleFactor(hwnd); - m_reactNativeWin32App.ReactNativeIsland().ScaleFactor(scaleFactor); - - UpdateRootViewSizeToAppWindow(reactNativeIsland, m_reactNativeWin32App.AppWindow()); - - m_reactNativeWin32App.as().get()->DesktopChildSiteBridge( - std::move(desktopChildSiteBridge)); - return m_reactNativeWin32App; } diff --git a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.h b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.h index 1a077f0be38..309d4f5dd86 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.h +++ b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.h @@ -1,6 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma once + #include "ReactNativeAppBuilder.g.h" -#include namespace winrt::Microsoft::ReactNative::implementation { struct ReactNativeAppBuilder : ReactNativeAppBuilderT { @@ -8,25 +10,15 @@ struct ReactNativeAppBuilder : ReactNativeAppBuilderT { ~ReactNativeAppBuilder(); - winrt::Microsoft::ReactNative::ReactNativeAppBuilder AddPackageProviders( - winrt::Windows::Foundation::Collections::IVector const - &packageProviders); - winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetReactInstanceSettings( - winrt::Microsoft::ReactNative::ReactInstanceSettings const &settings); - - // TODO: Currently, SetCompositor API is not exposed to the developer. - // Compositor depends on the DispatcherQueue created by DispatcherQueueController on a current thread - // or dedicated thread. So we also have to make a provision for setting DispatcherQueueController. + winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetDispatcherQueueController( + winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController); winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetCompositor( winrt::Microsoft::UI::Composition::Compositor const &compositor); winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetAppWindow( winrt::Microsoft::UI::Windowing::AppWindow const &appWindow); - winrt::Microsoft::ReactNative::ReactNativeAppBuilder SetReactViewOptions( - winrt::Microsoft::ReactNative::ReactViewOptions const &reactViewOptions); winrt::Microsoft::ReactNative::ReactNativeWin32App Build(); private: - winrt::Microsoft::ReactNative::ReactViewOptions m_reactViewOptions{}; winrt::Microsoft::ReactNative::ReactNativeWin32App m_reactNativeWin32App{nullptr}; }; } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.idl b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.idl index 616cf035b57..b1940549616 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.idl +++ b/vnext/Microsoft.ReactNative/ReactNativeAppBuilder.idl @@ -9,60 +9,39 @@ import "IReactPackageProvider.idl"; namespace Microsoft.ReactNative { [experimental] - DOC_STRING("ReactNativeWin32App is used to manage resources appropriately to be able to host ReactNative component in a contentIsland.") + DOC_STRING("ReactNativeWin32App sets up the infrastructure for the default experience of a ReactNative application filling a WinAppSDK window.") runtimeclass ReactNativeWin32App { // Properties Microsoft.UI.Windowing.AppWindow AppWindow {get;}; - Microsoft.UI.Composition.Compositor Compositor {get;}; - - Microsoft.UI.Content.DesktopChildSiteBridge DesktopChildSiteBridge {get;}; - ReactNativeHost ReactNativeHost {get;}; - ReactNativeIsland ReactNativeIsland {get;}; + ReactViewOptions ReactViewOptions {get;}; + + // TODO: Hide these APIs for now until we're sure we need to expose them and can do so safely + // Microsoft.UI.Composition.Compositor Compositor {get;}; + // Microsoft.UI.Content.DesktopChildSiteBridge DesktopChildSiteBridge {get;}; + // Microsoft.UI.Dispatching DispatcherQueueController {get;}; + // ReactNativeIsland ReactNativeIsland {get;}; // Methods void Start(); } [experimental] - DOC_STRING("This is the builder for creating ReactInstanceSettings.") - runtimeclass ReactInstanceSettingsBuilder - { - ReactInstanceSettingsBuilder(); - - // Properties - ReactInstanceSettings ReactInstanceSettings {get;}; - - // Methods - ReactInstanceSettingsBuilder UseDirectDebugger(Boolean state); - - ReactInstanceSettingsBuilder UseDeveloperSupport(Boolean state); - - ReactInstanceSettingsBuilder BundleRootPath(String path); - - ReactInstanceSettingsBuilder DebugBundlePath(String path); - - ReactInstanceSettingsBuilder JavaScriptBundleFile(String file); - - ReactInstanceSettingsBuilder UseFastRefresh(Boolean state); - } - - [experimental] - DOC_STRING("ReactNativeAppBuilder initializes all the required infrastructure for a Win32 Fabric Application.") + DOC_STRING("ReactNativeAppBuilder builds a ReactNativeWin32App with the base WinAppSDK infrastructure.") runtimeclass ReactNativeAppBuilder { ReactNativeAppBuilder(); // Methods - ReactNativeAppBuilder AddPackageProviders(Windows.Foundation.Collections.IVector packageProviders); - - ReactNativeAppBuilder SetReactInstanceSettings(ReactInstanceSettings settings); ReactNativeAppBuilder SetAppWindow(Microsoft.UI.Windowing.AppWindow appWindow); - ReactNativeAppBuilder SetReactViewOptions(Microsoft.ReactNative.ReactViewOptions reactViewOptions); + // TODO: Hide these APIs for now until we're sure we need to expose them and can do so safely + // Compositor depends on the DispatcherQueue created by DispatcherQueueController + // ReactNativeAppBuilder SetCompositor(Microsoft.UI.Composition.Compositor compositor); + // ReactNativeAppBuilder SetDispatcherQueueController(Microsoft.UI.Dispatching DispatcherQueueController); ReactNativeWin32App Build(); } diff --git a/vnext/Microsoft.ReactNative/ReactNativeWin32App.cpp b/vnext/Microsoft.ReactNative/ReactNativeWin32App.cpp index 0477c740fb6..0ed20cb1251 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeWin32App.cpp +++ b/vnext/Microsoft.ReactNative/ReactNativeWin32App.cpp @@ -1,12 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + #include "pch.h" #include "ReactNativeWin32App.h" #include "ReactNativeWin32App.g.cpp" + +#include "IReactDispatcher.h" +#include "ReactCoreInjection.h" #include "ReactNativeHost.h" + #include "winrt/Microsoft.UI.Composition.h" #include "winrt/Microsoft.UI.Content.h" +#include "winrt/Microsoft.UI.Dispatching.h" #include "winrt/Microsoft.UI.Interop.h" #include "winrt/Microsoft.UI.Windowing.h" +// Scaling factor for the window's content based on the DPI of the display where the window is located. +float ScaleFactor(HWND hwnd) noexcept { + return GetDpiForWindow(hwnd) / static_cast(USER_DEFAULT_SCREEN_DPI); +} + +void UpdateRootViewSizeToAppWindow( + winrt::Microsoft::ReactNative::ReactNativeIsland const &rootView, + winrt::Microsoft::UI::Windowing::AppWindow const &window) { + auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); + auto scaleFactor = ScaleFactor(hwnd); + winrt::Windows::Foundation::Size size{ + window.ClientSize().Width / scaleFactor, window.ClientSize().Height / scaleFactor}; + // Do not relayout when minimized + if (window.Presenter().as().State() != + winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { + winrt::Microsoft::ReactNative::LayoutConstraints constraints; + constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined; + constraints.MaximumSize = constraints.MinimumSize = size; + rootView.Arrange(constraints, {0, 0}); + } +} + namespace winrt::Microsoft::ReactNative::implementation { ReactNativeWin32App::ReactNativeWin32App() {} @@ -14,41 +44,44 @@ ReactNativeWin32App::~ReactNativeWin32App() { m_desktopChildSiteBridge = nullptr; // Destroy all Composition objects - m_compositor.Close(); - m_compositor = nullptr; -} - -void ReactNativeWin32App::AppWindow(winrt::Microsoft::UI::Windowing::AppWindow const &appWindow) { - m_appWindow = appWindow; + if (m_compositor != nullptr) { + m_compositor.Close(); + m_compositor = nullptr; + } } winrt::Microsoft::UI::Windowing::AppWindow ReactNativeWin32App::AppWindow() { return m_appWindow; } -void ReactNativeWin32App::Compositor(winrt::Microsoft::UI::Composition::Compositor const &compositor) { - m_compositor = compositor; +void ReactNativeWin32App::AppWindow(winrt::Microsoft::UI::Windowing::AppWindow const &appWindow) { + m_appWindow = appWindow; } winrt::Microsoft::UI::Composition::Compositor ReactNativeWin32App::Compositor() { return m_compositor; } -winrt::Microsoft::ReactNative::ReactNativeIsland ReactNativeWin32App::ReactNativeIsland() { - return m_reactNativeIsland; +void ReactNativeWin32App::Compositor(winrt::Microsoft::UI::Composition::Compositor const &compositor) { + m_compositor = compositor; } -void ReactNativeWin32App::ReactNativeIsland(winrt::Microsoft::ReactNative::ReactNativeIsland const &reactNativeIsland) { - m_reactNativeIsland = reactNativeIsland; +void ReactNativeWin32App::DesktopChildSiteBridge( + winrt::Microsoft::UI::Content::DesktopChildSiteBridge const &desktopChildSiteBridge) { + m_desktopChildSiteBridge = desktopChildSiteBridge; } winrt::Microsoft::UI::Content::DesktopChildSiteBridge ReactNativeWin32App::DesktopChildSiteBridge() { return m_desktopChildSiteBridge; } -void ReactNativeWin32App::DesktopChildSiteBridge( - winrt::Microsoft::UI::Content::DesktopChildSiteBridge const &desktopChildSiteBridge) { - m_desktopChildSiteBridge = desktopChildSiteBridge; +winrt::Microsoft::UI::Dispatching::DispatcherQueueController ReactNativeWin32App::DispatcherQueueController() { + return m_dispatcherQueueController; +} + +void ReactNativeWin32App::DispatcherQueueController( + winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController) { + m_dispatcherQueueController = dispatcherQueueController; } winrt::Microsoft::ReactNative::ReactNativeHost ReactNativeWin32App::ReactNativeHost() { @@ -62,12 +95,90 @@ void ReactNativeWin32App::ReactNativeHost(winrt::Microsoft::ReactNative::ReactNa m_host = host; } -void ReactNativeWin32App::DispatchQueueController( - winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController) { - m_dispatcherQueueController = dispatcherQueueController; +winrt::Microsoft::ReactNative::ReactNativeIsland ReactNativeWin32App::ReactNativeIsland() { + return m_reactNativeIsland; +} + +void ReactNativeWin32App::ReactNativeIsland(winrt::Microsoft::ReactNative::ReactNativeIsland const &reactNativeIsland) { + m_reactNativeIsland = reactNativeIsland; +} + +winrt::Microsoft::ReactNative::ReactViewOptions ReactNativeWin32App::ReactViewOptions() { + if (m_reactViewOptions == nullptr) { + m_reactViewOptions = winrt::make(); + } + return m_reactViewOptions; +} + +void ReactNativeWin32App::ReactViewOptions(winrt::Microsoft::ReactNative::ReactViewOptions const &viewOptions) { + m_reactViewOptions = viewOptions; } void ReactNativeWin32App::Start() { + // Show the hosting AppWindow + m_appWindow.Show(); + + // Currently set the property to use current thread dispatcher as a default UI dispatcher. + // TODO: Use the correct dispatcher from a developer-provided DispatcherQueueController + ReactNativeHost().InstanceSettings().Properties().Set( + ReactDispatcherHelper::UIDispatcherProperty(), ReactDispatcherHelper::UIThreadDispatcher()); + + auto hwnd{winrt::Microsoft::UI::GetWindowFromWindowId(m_appWindow.Id())}; + + winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId( + ReactNativeHost().InstanceSettings().Properties(), reinterpret_cast(hwnd)); + + winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositor( + ReactNativeHost().InstanceSettings(), m_compositor); + + // Start the react-native instance, which will create a JavaScript runtime and load the applications bundle. + ReactNativeHost().ReloadInstance(); + + // Create a RootView which will present a react-native component + if (m_reactNativeIsland == nullptr) { + m_reactNativeIsland = winrt::Microsoft::ReactNative::ReactNativeIsland(m_compositor); + } + + m_reactNativeIsland.ReactViewHost( + winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(ReactNativeHost(), ReactViewOptions())); + + // Update the size of the RootView when the AppWindow changes size + m_appWindow.Changed([wkRootView = winrt::make_weak(m_reactNativeIsland)]( + winrt::Microsoft::UI::Windowing::AppWindow const &window, + winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs const &args) { + if (args.DidSizeChange() || args.DidVisibilityChange()) { + if (auto rootView = wkRootView.get()) { + UpdateRootViewSizeToAppWindow(rootView, window); + } + } + }); + + // Quit application when main window is closed + m_appWindow.Destroying([this]( + winrt::Microsoft::UI::Windowing::AppWindow const &window, + winrt::Windows::Foundation::IInspectable const & /*args*/) { + // Before we shutdown the application - unload the ReactNativeHost to give the javascript a chance to save any + // state + auto async = ReactNativeHost().UnloadInstance(); + async.Completed([this](auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) { + assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); + ReactNativeHost().InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); }); + }); + }); + + // DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland + m_desktopChildSiteBridge = + winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(m_compositor, m_appWindow.Id()); + + m_desktopChildSiteBridge.Connect(m_reactNativeIsland.Island()); + + m_desktopChildSiteBridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); + + auto scaleFactor = ScaleFactor(hwnd); + m_reactNativeIsland.ScaleFactor(scaleFactor); + + UpdateRootViewSizeToAppWindow(m_reactNativeIsland, m_appWindow); + m_desktopChildSiteBridge.Show(); // Run the main application event loop diff --git a/vnext/Microsoft.ReactNative/ReactNativeWin32App.h b/vnext/Microsoft.ReactNative/ReactNativeWin32App.h index bb424cad169..dea84c1cdf3 100644 --- a/vnext/Microsoft.ReactNative/ReactNativeWin32App.h +++ b/vnext/Microsoft.ReactNative/ReactNativeWin32App.h @@ -1,5 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. #pragma once + #include "ReactNativeWin32App.g.h" + #include "winrt/Microsoft.UI.Dispatching.h" namespace winrt::Microsoft::ReactNative::implementation { @@ -13,26 +17,31 @@ struct ReactNativeWin32App : ReactNativeWin32AppT { winrt::Microsoft::UI::Composition::Compositor Compositor(); void Compositor(winrt::Microsoft::UI::Composition::Compositor const &compositor); - winrt::Microsoft::ReactNative::ReactNativeIsland ReactNativeIsland(); - void ReactNativeIsland(winrt::Microsoft::ReactNative::ReactNativeIsland const &reactNativeIsland); - winrt::Microsoft::UI::Content::DesktopChildSiteBridge DesktopChildSiteBridge(); void DesktopChildSiteBridge(winrt::Microsoft::UI::Content::DesktopChildSiteBridge const &desktopChildSiteBridge); - void DispatchQueueController( + winrt::Microsoft::UI::Dispatching::DispatcherQueueController DispatcherQueueController(); + void DispatcherQueueController( winrt::Microsoft::UI::Dispatching::DispatcherQueueController const &dispatcherQueueController); winrt::Microsoft::ReactNative::ReactNativeHost ReactNativeHost(); void ReactNativeHost(winrt::Microsoft::ReactNative::ReactNativeHost const &host); + winrt::Microsoft::ReactNative::ReactNativeIsland ReactNativeIsland(); + void ReactNativeIsland(winrt::Microsoft::ReactNative::ReactNativeIsland const &reactNativeIsland); + + winrt::Microsoft::ReactNative::ReactViewOptions ReactViewOptions(); + void ReactViewOptions(winrt::Microsoft::ReactNative::ReactViewOptions const &reactViewOptions); + void Start(); private: winrt::Microsoft::UI::Windowing::AppWindow m_appWindow{nullptr}; winrt::Microsoft::UI::Composition::Compositor m_compositor{nullptr}; + winrt::Microsoft::UI::Content::DesktopChildSiteBridge m_desktopChildSiteBridge{nullptr}; winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{nullptr}; winrt::Microsoft::ReactNative::ReactNativeHost m_host{nullptr}; winrt::Microsoft::ReactNative::ReactNativeIsland m_reactNativeIsland{nullptr}; - winrt::Microsoft::UI::Content::DesktopChildSiteBridge m_desktopChildSiteBridge{nullptr}; + winrt::Microsoft::ReactNative::ReactViewOptions m_reactViewOptions{nullptr}; }; } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Shared/Shared.vcxitems b/vnext/Shared/Shared.vcxitems index 00bff89a92d..3db114614e6 100644 --- a/vnext/Shared/Shared.vcxitems +++ b/vnext/Shared/Shared.vcxitems @@ -232,11 +232,6 @@ true $(ReactNativeWindowsDir)Microsoft.ReactNative\ReactNativeAppBuilder.idl Code - - - true - $(ReactNativeWindowsDir)Microsoft.ReactNative\ReactNativeAppBuilder.idl - Code @@ -349,11 +344,6 @@ true $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\ReactNativeAppBuilder.idl Code - - - true - $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\ReactNativeAppBuilder.idl - Code $(MSBuildThisFileDirectory)..\Microsoft.ReactNative\IJSValueReader.idl diff --git a/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp b/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp index eed4ef509dc..e84bd5cdb3c 100644 --- a/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp +++ b/vnext/templates/cpp-app/windows/MyApp/MyApp.cpp @@ -8,6 +8,7 @@ #include "NativeModules.h" +// A PackageProvider containing any turbo modules you define within this app project struct CompReactPackageProvider : winrt::implements { public: // IReactPackageProvider @@ -16,152 +17,66 @@ struct CompReactPackageProvider } }; -// Global Variables: -constexpr PCWSTR windowTitle = L"{{ mainComponentName }}"; -constexpr PCWSTR mainComponentName = L"{{ mainComponentName }}"; - -float ScaleFactor(HWND hwnd) noexcept { - return GetDpiForWindow(hwnd) / static_cast(USER_DEFAULT_SCREEN_DPI); -} +// The entry point of the Win32 application +_Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR /* commandLine */, int showCmd) { + // Initialize WinRT + winrt::init_apartment(winrt::apartment_type::single_threaded); -void UpdateRootViewSizeToAppWindow( - winrt::Microsoft::ReactNative::ReactNativeIsland const &rootView, - winrt::Microsoft::UI::Windowing::AppWindow const &window) { - auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); - auto scaleFactor = ScaleFactor(hwnd); - winrt::Windows::Foundation::Size size{ - window.ClientSize().Width / scaleFactor, window.ClientSize().Height / scaleFactor}; - // Do not relayout when minimized - if (window.Presenter().as().State() != - winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) { - winrt::Microsoft::ReactNative::LayoutConstraints constraints; - constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined; - constraints.MaximumSize = constraints.MinimumSize = size; - rootView.Arrange(constraints, {0,0}); - } -} + // Enable per monitor DPI scaling + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); -// Create and configure the ReactNativeHost -winrt::Microsoft::ReactNative::ReactNativeHost CreateReactNativeHost( - HWND hwnd, - const winrt::Microsoft::UI::Composition::Compositor &compositor) { + // Find the path hosting the app exe file WCHAR appDirectory[MAX_PATH]; GetModuleFileNameW(NULL, appDirectory, MAX_PATH); PathCchRemoveFileSpec(appDirectory, MAX_PATH); - auto host = winrt::Microsoft::ReactNative::ReactNativeHost(); - - // Include any autolinked modules - RegisterAutolinkedNativeModulePackages(host.PackageProviders()); + // Create a ReactNativeWin32App with the ReactNativeAppBuilder + auto reactNativeWin32App{winrt::Microsoft::ReactNative::ReactNativeAppBuilder().Build()}; - host.PackageProviders().Append(winrt::make()); + // Configure the initial InstanceSettings for the app's ReactNativeHost + auto settings{reactNativeWin32App.ReactNativeHost().InstanceSettings()}; + // Register any autolinked native modules + RegisterAutolinkedNativeModulePackages(settings.PackageProviders()); + // Register any native modules defined within this app project + settings.PackageProviders().Append(winrt::make()); #if BUNDLE - host.InstanceSettings().JavaScriptBundleFile(L"index.windows"); - host.InstanceSettings().BundleRootPath(std::wstring(L"file://").append(appDirectory).append(L"\\Bundle\\").c_str()); - host.InstanceSettings().UseFastRefresh(false); + // Load the JS bundle from a file (not Metro): + // Set the path (on disk) where the .bundle file is located + settings.BundleRootPath(std::wstring(L"file://").append(appDirectory).append(L"\\Bundle\\").c_str()); + // Set the name of the bundle file (without the .bundle extension) + settings.JavaScriptBundleFile(L"index.windows"); + // Disable hot reload + settings.UseFastRefresh(false); #else - host.InstanceSettings().JavaScriptBundleFile(L"index"); - host.InstanceSettings().UseFastRefresh(true); + // Load the JS bundle from Metro + settings.JavaScriptBundleFile(L"index"); + // Enable hot reload + settings.UseFastRefresh(true); #endif - #if _DEBUG - host.InstanceSettings().UseDirectDebugger(true); - host.InstanceSettings().UseDeveloperSupport(true); + // For Debug builds + // Enable Direct Debugging of JS + settings.UseDirectDebugger(true); + // Enable the Developer Menu + settings.UseDeveloperSupport(true); #else - host.InstanceSettings().UseDirectDebugger(false); - host.InstanceSettings().UseDeveloperSupport(false); + // For Release builds: + // Disable Direct Debugging of JS + settings.UseDirectDebugger(false); + // Disable the Developer Menu + settings.UseDeveloperSupport(false); #endif - winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId( - host.InstanceSettings().Properties(), reinterpret_cast(hwnd)); - - winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositor( - host.InstanceSettings(), compositor); - - return host; -} - -_Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR /* commandLine */, int showCmd) { - // Initialize WinRT. - winrt::init_apartment(winrt::apartment_type::single_threaded); - - // Enable per monitor DPI scaling - SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - - // Create a DispatcherQueue for this thread. This is needed for Composition, Content, and - // Input APIs. - auto dispatcherQueueController{winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread()}; - - // Create a Compositor for all Content on this thread. - auto compositor{winrt::Microsoft::UI::Composition::Compositor()}; - - // Create a top-level window. - auto window = winrt::Microsoft::UI::Windowing::AppWindow::Create(); - window.Title(windowTitle); - window.Resize({1000, 1000}); - window.Show(); - auto hwnd = winrt::Microsoft::UI::GetWindowFromWindowId(window.Id()); - auto scaleFactor = ScaleFactor(hwnd); - - auto host = CreateReactNativeHost(hwnd, compositor); - - // Start the react-native instance, which will create a JavaScript runtime and load the applications bundle - host.ReloadInstance(); - - // Create a RootView which will present a react-native component - winrt::Microsoft::ReactNative::ReactViewOptions viewOptions; - viewOptions.ComponentName(mainComponentName); - auto rootView = winrt::Microsoft::ReactNative::ReactNativeIsland(compositor); - rootView.ReactViewHost(winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(host, viewOptions)); - - // Update the size of the RootView when the AppWindow changes size - window.Changed([wkRootView = winrt::make_weak(rootView)]( - winrt::Microsoft::UI::Windowing::AppWindow const &window, - winrt::Microsoft::UI::Windowing::AppWindowChangedEventArgs const &args) { - if (args.DidSizeChange() || args.DidVisibilityChange()) { - if (auto rootView = wkRootView.get()) { - UpdateRootViewSizeToAppWindow(rootView, window); - } - } - }); - - // Quit application when main window is closed - window.Destroying( - [host](winrt::Microsoft::UI::Windowing::AppWindow const &window, winrt::IInspectable const & /*args*/) { - // Before we shutdown the application - unload the ReactNativeHost to give the javascript a chance to save any - // state - auto async = host.UnloadInstance(); - async.Completed([host](auto asyncInfo, winrt::Windows::Foundation::AsyncStatus asyncStatus) { - asyncStatus; - assert(asyncStatus == winrt::Windows::Foundation::AsyncStatus::Completed); - host.InstanceSettings().UIDispatcher().Post([]() { PostQuitMessage(0); }); - }); - }); - - // DesktopChildSiteBridge create a ContentSite that can host the RootView ContentIsland - auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(compositor, window.Id()); - bridge.Connect(rootView.Island()); - bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); - - rootView.ScaleFactor(scaleFactor); - - // Set the intialSize of the root view - UpdateRootViewSizeToAppWindow(rootView, window); - - bridge.Show(); - - // Run the main application event loop - dispatcherQueueController.DispatcherQueue().RunEventLoop(); - - // Rundown the DispatcherQueue. This drains the queue and raises events to let components - // know the message loop has finished. - dispatcherQueueController.ShutdownQueue(); + // Get the AppWindow so we can configure its initial title and size + auto appWindow{reactNativeWin32App.AppWindow()}; + appWindow.Title(L"{{ mainComponentName }}"); + appWindow.Resize({1000, 1000}); - bridge.Close(); - bridge = nullptr; + // Get the ReactViewOptions so we can set the initial RN component to load + auto viewOptions{reactNativeWin32App.ReactViewOptions()}; + viewOptions.ComponentName(L"{{ mainComponentName }}"); - // Destroy all Composition objects - compositor.Close(); - compositor = nullptr; + // Start the app + reactNativeWin32App.Start(); }