Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fabric] Get Modal to host RN components in new hwnd #13500

Merged
merged 53 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d0da31e
save state
TatianaKapos Jun 12, 2024
e3f75ed
add example
TatianaKapos Jun 12, 2024
5b9120e
build but blank page still :(
TatianaKapos Jun 25, 2024
838f0cc
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Jul 24, 2024
6bf40e5
clean up comments
TatianaKapos Jul 25, 2024
258eaf4
visuals show up in new hwnd!
TatianaKapos Jul 30, 2024
a57fdb2
clean up code
TatianaKapos Jul 30, 2024
0fc2952
better naming and unfork Modal examples
TatianaKapos Jul 31, 2024
700d3c1
Merge branch 'main' into tk-ModalHosting
TatianaKapos Aug 1, 2024
cbd2eab
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Aug 21, 2024
fa33925
Merge branch 'tk-ModalHosting' of https://github.com/TatianaKapos/rea…
TatianaKapos Aug 27, 2024
9c4428d
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Aug 27, 2024
216ea91
testing save state
TatianaKapos Sep 4, 2024
568aab4
Make the RN island a Modal member var
danielayala94 Sep 5, 2024
c71a292
Failed attempt at skipping root view in CEH, leaving it for learning …
danielayala94 Sep 5, 2024
2528244
you can click on UI!
TatianaKapos Sep 18, 2024
71e8e4c
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Sep 18, 2024
cf7497d
clean up code
TatianaKapos Sep 19, 2024
a68b564
Change files
TatianaKapos Sep 19, 2024
5a8bea9
save state
TatianaKapos Sep 20, 2024
05f0168
remove hardcoded rootTag
TatianaKapos Sep 20, 2024
4578981
add width/height to example
TatianaKapos Sep 20, 2024
3fa6449
add test
TatianaKapos Sep 20, 2024
64cd1db
revert simple.tsx
TatianaKapos Sep 20, 2024
c08f135
remove test
TatianaKapos Sep 23, 2024
a5518aa
update snapshot
TatianaKapos Sep 24, 2024
adc80e5
Merge branch 'main' into tk-ModalHosting
TatianaKapos Sep 24, 2024
bae9ee2
feedback part 1: make Modal a RootComponentView
TatianaKapos Sep 26, 2024
a0bfea0
Merge branch 'tk-ModalHosting' of https://github.com/TatianaKapos/rea…
TatianaKapos Sep 26, 2024
29c40fe
feedback part2: simplify MountChildren
TatianaKapos Sep 27, 2024
c33df22
fix deleting modal
TatianaKapos Oct 1, 2024
39ddd0c
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Oct 1, 2024
c24294d
feedback round2
TatianaKapos Oct 3, 2024
96e6e81
merge in main
TatianaKapos Oct 3, 2024
18d4948
remove comment
TatianaKapos Oct 3, 2024
28378d7
remove imports
TatianaKapos Oct 3, 2024
38e4989
Merge branch 'main' into tk-ModalHosting
TatianaKapos Oct 3, 2024
1ee42fa
Merge branch 'main' into tk-ModalHosting
TatianaKapos Oct 4, 2024
171fcc8
Merge branch 'main' into tk-ModalHosting
TatianaKapos Oct 7, 2024
ad884dd
feedback part 3
TatianaKapos Oct 9, 2024
579a87a
merge
TatianaKapos Oct 9, 2024
36fca77
merge main
TatianaKapos Oct 9, 2024
3dd2a27
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Oct 10, 2024
5d36cb6
fix overrides
TatianaKapos Oct 10, 2024
1327a7e
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Oct 11, 2024
96f1bb2
add simple layout - still has issues with padding/flex
TatianaKapos Oct 22, 2024
2a73dc0
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Oct 22, 2024
bfaafce
feedback part4
TatianaKapos Oct 23, 2024
f86bf68
lint
TatianaKapos Oct 23, 2024
d217fa4
update overrides
TatianaKapos Oct 23, 2024
2f378a6
Change files
TatianaKapos Oct 23, 2024
f3ab7ac
feedback
TatianaKapos Oct 31, 2024
3cb04af
Merge branch 'main' of https://github.com/TatianaKapos/react-native-w…
TatianaKapos Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ const Components: Array<RNTesterModuleInfo> = [
key: 'ImageWin32Test',
module: require('@office-iss/react-native-win32/Libraries/Image/Tests/ImageWin32Test'),
},
{
key: 'ModalExample',
category: 'UI',
module: require('../examples/Modal/ModalExample'),
},
/*
{
key: 'JSResponderHandlerExample',
Expand All @@ -68,11 +73,6 @@ const Components: Array<RNTesterModuleInfo> = [
key: 'KeyboardAvoidingViewExample',
module: require('../examples/KeyboardAvoidingView/KeyboardAvoidingViewExample'),
},
{
key: 'ModalExample',
category: 'UI',
module: require('../examples/Modal/ModalExample'),
},
{
key: 'NewAppScreenExample',
module: require('../examples/NewAppScreen/NewAppScreenExample'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ const Components: Array<RNTesterModuleInfo> = [
category: 'UI',
module: require('../examples-win/Glyph/GlyphExample'),
},
// {
// key: 'ModalExample',
// category: 'UI',
// module: require('../examples/Modal/ModalExample'),
// },
{
key: 'ModalExample',
category: 'UI',
module: require('../examples/Modal/ModalExample'),
},
{
key: 'Native Component',
module: require('../examples-win/NativeComponents/NativeComponent'),
Expand Down
36 changes: 27 additions & 9 deletions packages/playground/Samples/simple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,36 @@
* @format
*/
import React from 'react';
import {AppRegistry, View} from 'react-native';

export default class Bootstrap extends React.Component {
render() {
return (
import {
AppRegistry,
View,
Button,
SafeAreaView,
Modal,
Text,
} from 'react-native';
const Bootstrap = () => {
const [modalVisible, setModalVisible] = React.useState(false);
return (
<SafeAreaView>
<Button
title="Open Modal"
onPress={() => setModalVisible(!modalVisible)}
/>
<Text style={{textAlign:'center'}}>Modal visibility: {String(modalVisible)}</Text>
<Modal visible={modalVisible} style={{backgroundColor: 'yellow'}}>
<SafeAreaView style={{backgroundColor: 'yellow', width: 200, height: 200}}>
<Text style={{backgroundColor: 'green', width: 100, height: 100}}>I should be in the Modal!</Text>
<Text style={{backgroundColor: 'green', width: 100, height: 100}}>I should be in the Modal 2!</Text>
<Button title='Close Modal' onPress={() => setModalVisible(!modalVisible)}/>
</SafeAreaView>
</Modal>
<View
accessible={true}
style={{borderRadius: 30, width: 60, height: 60, margin: 10}}>
<View style={{backgroundColor: 'magenta', width: 60, height: 60}} />
</View>
);
}
}

</SafeAreaView>
);
};
AppRegistry.registerComponent('Bootstrap', () => Bootstrap);
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@
#include "../CompositionDynamicAutomationProvider.h"
#include "Unicode.h"

#include <DispatcherQueue.h>
#include <Fabric/Composition/CompositionContextHelper.h>
#include <Fabric/Composition/CompositionUIService.h>
#include <windows.ui.composition.interop.h>
#include <winrt/Microsoft.ReactNative.Composition.Experimental.h>
#include <winrt/Windows.UI.Composition.Desktop.h>
#include <winrt/Windows.UI.Composition.h>
#include "IReactContext.h"
#include "ReactHost/ReactInstanceWin.h"
#include "ReactNativeHost.h"
#include <winrt/Microsoft.UI.Content.h>
#include <winrt/Microsoft.UI.interop.h>

namespace winrt::Microsoft::ReactNative::Composition::implementation {
WindowsModalHostComponentView::WindowsModalHostComponentView(
Expand All @@ -29,7 +35,8 @@ WindowsModalHostComponentView::WindowsModalHostComponentView(
reactContext,
ComponentViewFeatures::Default & ~ComponentViewFeatures::Background,
false) {
m_context = reactContext; // save context
m_reactContext = reactContext; // save react context
m_compositionContext = compContext; // save composition context
}

winrt::Microsoft::ReactNative::ComponentView WindowsModalHostComponentView::Create(
Expand All @@ -39,34 +46,29 @@ winrt::Microsoft::ReactNative::ComponentView WindowsModalHostComponentView::Crea
return winrt::make<WindowsModalHostComponentView>(compContext, tag, reactContext);
}

// constants for creating a new windows (code mostly taken from LogBox)
// constants for creating a new windows
constexpr PCWSTR c_modalWindowClassName = L"MS_REACTNATIVE_MODAL";
constexpr auto CompHostProperty = L"CompHost";
const int MODAL_DEFAULT_WIDTH = 500;
const int MODAL_DEFAULT_HEIGHT = 500;

// creates a new modal window
void WindowsModalHostComponentView::EnsureModalCreated() {
auto host =
winrt::Microsoft::ReactNative::implementation::ReactNativeHost::GetReactNativeHost(m_context.Properties());
auto host = winrt::Microsoft::ReactNative::implementation::ReactNativeHost::GetReactNativeHost(m_reactContext.Properties());

// return if hwnd already exists
if (!host || m_hwnd) {
return;
}

RegisterWndClass(); // creates and register a windows class
auto CompositionHwndHost = winrt::Microsoft::ReactNative::CompositionHwndHost();
winrt::Microsoft::ReactNative::ReactViewOptions viewOptions;
viewOptions.ComponentName(L"Modal");
CompositionHwndHost.ReactViewHost(winrt::Microsoft::ReactNative::ReactCoreInjection::MakeViewHost(host, viewOptions));
RegisterWndClass();

HINSTANCE hInstance = GetModuleHandle(NULL);
winrt::impl::abi<winrt::Microsoft::ReactNative::ICompositionHwndHost>::type *pHost{nullptr};
winrt::com_ptr<::IUnknown> spunk;
CompositionHwndHost.as(spunk);

// get the root hwnd
auto roothwnd = reinterpret_cast<HWND>(
winrt::Microsoft::ReactNative::ReactCoreInjection::GetTopLevelWindowId(m_context.Properties().Handle()));
winrt::Microsoft::ReactNative::ReactCoreInjection::GetTopLevelWindowId(m_reactContext.Properties().Handle()));

m_hwnd = CreateWindow(
c_modalWindowClassName,
Expand All @@ -86,6 +88,25 @@ void WindowsModalHostComponentView::EnsureModalCreated() {
throw std::exception("Failed to create new hwnd for Modal: " + GetLastError());
}

// set the top-level windows as the new hwnd
winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(host.InstanceSettings().Properties(), reinterpret_cast<uint64_t>(m_hwnd));

// get current compositor - handles the creation/manipulation of visual objects
auto compositionContext = winrt::Microsoft::ReactNative::Composition::implementation::CompositionUIService::GetCompositionContext(m_reactContext.Properties().Handle());
auto compositor = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(compositionContext);

// create a react native island - code taken from CompositionHwndHost
auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(compositor, winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd));
auto reactNativeIsland = winrt::Microsoft::ReactNative::ReactNativeIsland(compositor);
auto contentIsland = reactNativeIsland.Island();
bridge.Connect(contentIsland);
bridge.Show();
bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow);
m_rootVisual = reactNativeIsland.RootVisual().try_as<winrt::Microsoft::UI::Composition::ContainerVisual>();

// set ScaleFactor
reactNativeIsland.ScaleFactor(GetDpiForWindow(m_hwnd) / 96.0f);
TatianaKapos marked this conversation as resolved.
Show resolved Hide resolved

spunk.detach();
}

Expand All @@ -99,7 +120,7 @@ void WindowsModalHostComponentView::ShowOnUIThread() {

void WindowsModalHostComponentView::HideOnUIThread() noexcept {
if (m_hwnd) {
::ShowWindow(m_hwnd, SW_HIDE);
SendMessage(m_hwnd, WM_CLOSE, 0, 0);
}
}

Expand All @@ -122,14 +143,13 @@ LRESULT CALLBACK ModalBoxWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM
}

switch (message) {
case WM_NCCREATE: { // sent before WM_CREATE, lparam should be identical to members of CreateWindowEx
case WM_NCCREATE: { // called before WM_CREATE, lparam should be identical to members of CreateWindowEx
auto createStruct = reinterpret_cast<CREATESTRUCT *>(lparam); // CreateStruct
data = static_cast<::IUnknown *>(createStruct->lpCreateParams);
SetProp(hwnd, CompHostProperty, data); // adds new properties to window
break;
}
case WM_CREATE: { // recieves after window is created but before visible
// host.Initialize((uint64_t)hwnd); cause Modal to throw a not registered error
case WM_CREATE: { // called after window is created but before visible
break;
}
case WM_CLOSE: {
Expand All @@ -138,7 +158,6 @@ LRESULT CALLBACK ModalBoxWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM
return 0;
}
case WM_DESTROY: { // called when we want to destroy the window
data->Release();
SetProp(hwnd, CompHostProperty, nullptr);
TatianaKapos marked this conversation as resolved.
Show resolved Hide resolved
break;
}
Expand Down Expand Up @@ -175,20 +194,35 @@ void WindowsModalHostComponentView::RegisterWndClass() noexcept {
registered = true;
}

// childComponentView - reference to the child component view
// index - the position in which the childComponentView should be mounted
void WindowsModalHostComponentView::MountChildComponentView(
const winrt::Microsoft::ReactNative::ComponentView &childComponentView,
uint32_t index) noexcept {
// Disabled due to partial Modal implementation. Tracking re-enablement with task list here:
// https://github.com/microsoft/react-native-windows/issues/11157 assert(false);
base_type::MountChildComponentView(childComponentView, index);
EnsureModalCreated();
m_children.InsertAt(index, childComponentView); // insert childComponent into m_children
// Sets the parent of the childComponentView to *this (the current instance of WindowsModalHostComponentView)
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(childComponentView)->parent(*this);
indexOffsetForBorder(index);
ensureVisual();
if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
auto containerChildren = m_rootVisual.as<winrt::Microsoft::UI::Composition::ContainerVisual>().Children();
auto compVisual = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual(compositionChild->OuterVisual());
if (index == 0) {
containerChildren.InsertAtBottom(compVisual);
return;
}
auto insertAfter = containerChildren.First();
for (uint32_t i = 1; i < index; i++)
insertAfter.MoveNext();
containerChildren.InsertAbove(compVisual, insertAfter.Current());
}
}

void WindowsModalHostComponentView::UnmountChildComponentView(
const winrt::Microsoft::ReactNative::ComponentView &childComponentView,
uint32_t index) noexcept {
// Disabled due to partial Modal implementation.Tracking re-enablement with task list here : https : //
// github.com/microsoft/react-native-windows/issues/11157 assert(false);
base_type::UnmountChildComponentView(childComponentView, index);
HideOnUIThread();
TatianaKapos marked this conversation as resolved.
Show resolved Hide resolved
}

void WindowsModalHostComponentView::HandleCommand(
TatianaKapos marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -204,7 +238,6 @@ void WindowsModalHostComponentView::updateProps(
*std::static_pointer_cast<const facebook::react::ModalHostViewProps>(oldProps ? oldProps : viewProps());
const auto &newModalProps = *std::static_pointer_cast<const facebook::react::ModalHostViewProps>(props);

// currently Modal only gets Destroyed by closing the window
if (newModalProps.visible) {
EnsureModalCreated();
ShowOnUIThread();
Expand All @@ -216,75 +249,8 @@ void WindowsModalHostComponentView::updateProps(
void WindowsModalHostComponentView::updateLayoutMetrics(
facebook::react::LayoutMetrics const &layoutMetrics,
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);

// Temporary placeholder for Modal, draws on main hwnd
if (m_layoutMetrics.frame.size != layoutMetrics.frame.size ||
m_layoutMetrics.pointScaleFactor != layoutMetrics.pointScaleFactor || m_layoutMetrics.frame.size.width == 0) {
// Always make visual a min size, so that even if its laid out at zero size, its clear an unimplemented view was
// rendered
float width = std::max(m_layoutMetrics.frame.size.width, 200.0f);
float height = std::max(m_layoutMetrics.frame.size.width, 50.0f);

winrt::Windows::Foundation::Size surfaceSize = {
width * m_layoutMetrics.pointScaleFactor, height * m_layoutMetrics.pointScaleFactor};
auto drawingSurface = m_compContext.CreateDrawingSurfaceBrush(
surfaceSize,
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);

drawingSurface.HorizontalAlignmentRatio(0.f);
drawingSurface.VerticalAlignmentRatio(0.f);
drawingSurface.Stretch(winrt::Microsoft::ReactNative::Composition::Experimental::CompositionStretch::None);
Visual().as<Experimental::ISpriteVisual>().Brush(drawingSurface);
Visual().Size(surfaceSize);
Visual().Offset({
layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
0.0f,
});

POINT offset;
{
::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(
drawingSurface, m_layoutMetrics.pointScaleFactor, &offset);
if (auto d2dDeviceContext = autoDraw.GetRenderTarget()) {
d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Blue, 0.3f));
assert(d2dDeviceContext->GetUnitMode() == D2D1_UNIT_MODE_DIPS);

float offsetX = static_cast<float>(offset.x / m_layoutMetrics.pointScaleFactor);
float offsetY = static_cast<float>(offset.y / m_layoutMetrics.pointScaleFactor);

winrt::com_ptr<IDWriteTextFormat> spTextFormat;
winrt::check_hresult(::Microsoft::ReactNative::DWriteFactory()->CreateTextFormat(
L"Segoe UI",
nullptr, // Font collection (nullptr sets it to use the system font collection).
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
12,
L"",
spTextFormat.put()));

winrt::com_ptr<ID2D1SolidColorBrush> textBrush;
winrt::check_hresult(
d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), textBrush.put()));

const D2D1_RECT_F rect = {
static_cast<float>(offset.x), static_cast<float>(offset.y), width + offset.x, height + offset.y};

auto label = ::Microsoft::Common::Unicode::Utf8ToUtf16(std::string("This is a Modal"));
d2dDeviceContext->DrawText(
label.c_str(),
static_cast<UINT32>(label.length()),
spTextFormat.get(),
rect,
textBrush.get(),
D2D1_DRAW_TEXT_OPTIONS_NONE,
DWRITE_MEASURING_MODE_NATURAL);
}
}
}
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
}

void WindowsModalHostComponentView::updateState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct WindowsModalHostComponentView

void updateProps(facebook::react::Props::Shared const &props, facebook::react::Props::Shared const &oldProps) noexcept
override;
void Visible(bool visible) noexcept;
void updateLayoutMetrics(
facebook::react::LayoutMetrics const &layoutMetrics,
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept override;
Expand All @@ -58,7 +59,11 @@ struct WindowsModalHostComponentView

private:
HWND m_hwnd{nullptr};
winrt::Microsoft::ReactNative::ReactContext m_context;
winrt::Microsoft::ReactNative::CompositionHwndHost m_compositionHwndHost;
winrt::Microsoft::ReactNative::ReactContext m_reactContext;
TatianaKapos marked this conversation as resolved.
Show resolved Hide resolved
winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext m_compositionContext;
winrt::Microsoft::UI::Composition::ContainerVisual m_rootVisual{nullptr};
facebook::react::ModalHostViewProps *m_ModalProps;
};

} // namespace winrt::Microsoft::ReactNative::Composition::implementation
Loading
Loading