Files
react-native-svg/apps/fabric-windows-example/windows/FabricExample/FabricExample.cpp
Andrew Coates d02c997352 fix: react-native-windows implementation for new architecture (#2527)
# Summary
There are two main things going on with the PR. Both involve a reworking
of the new arch implementation of rn-svg for windows.
The current implementation attempts to implement a svg renderer from
scratch. There are numerous edge cases that it wasn't handling
correctly, and the performance had some serious issues. This
implementation switches to use the svg rendering path built into
Direct2D. This brings significant performance improvements.

The 2nd issue is there have been various breaking changes in
react-native-windows for how new arch native components are implemented.
This brings the rn-svg implementation in line with those latest changes.

## Test Plan

Primary testing right now is loading up the example app in the repo.
New arch on react-native-windows is still in somewhat early days - so
there are not really current users of this code. I am integrating this
code into Microsoft Office, where I have tested some scenarios. But we
will get expanded testing as we roll out the new arch. I expect there to
be some follow-ups as we expand our usage. The version of rn-svg before
this PR doesn't build with the latest new arch react-native-windows
versions. - So its hard to get worse than that.

### What's required for testing (prerequisites)?

### What are the steps to reproduce (after prerequisites)?

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    N/A  |
| MacOS   |    N/A      |
| Android |    N/A      |
| Web     |          |

## Checklist

- [x] I have tested this on a device and a simulator
- [ ] I added documentation in `README.md`
- [ ] I updated the typed files (typescript)
- [ ] I added a test for the API in the `__tests__` folder
2024-11-15 12:17:12 +01:00

168 lines
6.6 KiB
C++

// FabricExample.cpp : Defines the entry point for the application.
//
#include "pch.h"
#include "FabricExample.h"
#include "AutolinkedNativeModules.g.h"
#include "NativeModules.h"
struct CompReactPackageProvider
: winrt::implements<CompReactPackageProvider, winrt::Microsoft::ReactNative::IReactPackageProvider> {
public: // IReactPackageProvider
void CreatePackage(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept {
AddAttributedModules(packageBuilder, true);
}
};
// Global Variables:
constexpr PCWSTR windowTitle = L"FabricExample";
constexpr PCWSTR mainComponentName = L"FabricExample";
float ScaleFactor(HWND hwnd) noexcept {
return GetDpiForWindow(hwnd) / static_cast<float>(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<winrt::Microsoft::UI::Windowing::OverlappedPresenter>().State() !=
winrt::Microsoft::UI::Windowing::OverlappedPresenterState::Minimized) {
winrt::Microsoft::ReactNative::LayoutConstraints constraints;
constraints.MaximumSize = constraints.MinimumSize = size;
constraints.LayoutDirection =
winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
rootView.Arrange(constraints, {0, 0});
}
}
// Create and configure the ReactNativeHost
winrt::Microsoft::ReactNative::ReactNativeHost CreateReactNativeHost(
HWND hwnd,
const winrt::Microsoft::UI::Composition::Compositor &compositor) {
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());
host.PackageProviders().Append(winrt::make<CompReactPackageProvider>());
#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);
#else
host.InstanceSettings().JavaScriptBundleFile(L"index");
host.InstanceSettings().UseFastRefresh(true);
#endif
#if _DEBUG
host.InstanceSettings().UseDirectDebugger(true);
host.InstanceSettings().UseDeveloperSupport(true);
#else
host.InstanceSettings().UseDirectDebugger(false);
host.InstanceSettings().UseDeveloperSupport(false);
#endif
winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
host.InstanceSettings().Properties(), reinterpret_cast<uint64_t>(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) {
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();
bridge.Close();
bridge = nullptr;
// Destroy all Composition objects
compositor.Close();
compositor = nullptr;
}