react-native-windows 0.76.2 → 0.76.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/Libraries/Components/View/View.windows.js +76 -52
  2. package/Libraries/Modal/Modal.windows.js +352 -0
  3. package/Libraries/Text/Text.windows.js +1 -1
  4. package/Microsoft.ReactNative/CompositionComponentView.idl +2 -1
  5. package/Microsoft.ReactNative/Fabric/AbiComponentDescriptor.cpp +4 -1
  6. package/Microsoft.ReactNative/Fabric/AbiViewComponentDescriptor.cpp +1 -1
  7. package/Microsoft.ReactNative/Fabric/AbiViewProps.cpp +7 -0
  8. package/Microsoft.ReactNative/Fabric/AbiViewProps.h +2 -0
  9. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +45 -50
  10. package/Microsoft.ReactNative/Fabric/ComponentView.h +14 -22
  11. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +943 -0
  12. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +80 -0
  13. package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +26 -3
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +187 -6
  15. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +10 -1
  16. package/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +8 -32
  17. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +336 -929
  18. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +32 -29
  19. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +9 -2
  20. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +2 -1
  21. package/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp +1 -1
  22. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +181 -123
  23. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +16 -8
  24. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +99 -37
  25. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +25 -3
  26. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +63 -2
  27. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +12 -0
  28. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +51 -3
  29. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +3 -0
  30. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +18 -8
  31. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +3 -0
  32. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +54 -5
  33. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +8 -0
  34. package/Microsoft.ReactNative/Fabric/Composition/Theme.cpp +9 -3
  35. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +11 -0
  36. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +2 -0
  37. package/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +1 -1
  38. package/Microsoft.ReactNative/Fabric/platform/react/renderer/components/view/HostPlatformViewTraitsInitializer.h +1 -5
  39. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +26 -0
  40. package/Microsoft.ReactNative/IReactViewComponentBuilder.idl +1 -1
  41. package/Microsoft.ReactNative/ReactNativeIsland.idl +3 -2
  42. package/Microsoft.ReactNative/ViewProps.idl +2 -0
  43. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +14 -0
  44. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  45. package/Shared/Shared.vcxitems +3 -0
  46. package/Shared/Shared.vcxitems.filters +1 -0
  47. package/codegen/NativeAccessibilityInfoSpec.g.h +1 -0
  48. package/codegen/NativeAccessibilityManagerSpec.g.h +1 -0
  49. package/codegen/NativeActionSheetManagerSpec.g.h +1 -0
  50. package/codegen/NativeAlertManagerSpec.g.h +1 -0
  51. package/codegen/NativeAnimatedModuleSpec.g.h +1 -0
  52. package/codegen/NativeAnimatedTurboModuleSpec.g.h +1 -0
  53. package/codegen/NativeAppStateSpec.g.h +1 -0
  54. package/codegen/NativeAppThemeSpec.g.h +1 -0
  55. package/codegen/NativeAppearanceSpec.g.h +1 -0
  56. package/codegen/NativeBlobModuleSpec.g.h +1 -0
  57. package/codegen/NativeBugReportingSpec.g.h +1 -0
  58. package/codegen/NativeClipboardSpec.g.h +1 -0
  59. package/codegen/NativeDOMSpec.g.h +1 -0
  60. package/codegen/NativeDevLoadingViewSpec.g.h +1 -0
  61. package/codegen/NativeDevMenuSpec.g.h +1 -0
  62. package/codegen/NativeDevSettingsSpec.g.h +1 -0
  63. package/codegen/NativeDevToolsSettingsManagerSpec.g.h +1 -0
  64. package/codegen/NativeDeviceEventManagerSpec.g.h +1 -0
  65. package/codegen/NativeDeviceInfoSpec.g.h +1 -0
  66. package/codegen/NativeDialogManagerAndroidSpec.g.h +1 -0
  67. package/codegen/NativeDialogManagerWindowsSpec.g.h +1 -0
  68. package/codegen/NativeExceptionsManagerSpec.g.h +1 -0
  69. package/codegen/NativeFileReaderModuleSpec.g.h +1 -0
  70. package/codegen/NativeFrameRateLoggerSpec.g.h +1 -0
  71. package/codegen/NativeHeadlessJsTaskSupportSpec.g.h +1 -0
  72. package/codegen/NativeI18nManagerSpec.g.h +1 -0
  73. package/codegen/NativeIdleCallbacksSpec.g.h +1 -0
  74. package/codegen/NativeImageEditorSpec.g.h +1 -0
  75. package/codegen/NativeImageLoaderAndroidSpec.g.h +1 -0
  76. package/codegen/NativeImageLoaderIOSSpec.g.h +1 -0
  77. package/codegen/NativeImageStoreAndroidSpec.g.h +1 -0
  78. package/codegen/NativeImageStoreIOSSpec.g.h +1 -0
  79. package/codegen/NativeIntentAndroidSpec.g.h +1 -0
  80. package/codegen/NativeIntersectionObserverSpec.g.h +1 -0
  81. package/codegen/NativeJSCHeapCaptureSpec.g.h +1 -0
  82. package/codegen/NativeJSCSamplingProfilerSpec.g.h +1 -0
  83. package/codegen/NativeKeyboardObserverSpec.g.h +1 -0
  84. package/codegen/NativeLinkingManagerSpec.g.h +1 -0
  85. package/codegen/NativeLogBoxSpec.g.h +1 -0
  86. package/codegen/NativeMicrotasksSpec.g.h +1 -0
  87. package/codegen/NativeModalManagerSpec.g.h +1 -0
  88. package/codegen/NativeMutationObserverSpec.g.h +1 -0
  89. package/codegen/NativeNetworkingAndroidSpec.g.h +1 -0
  90. package/codegen/NativeNetworkingIOSSpec.g.h +1 -0
  91. package/codegen/NativePerformanceObserverSpec.g.h +1 -0
  92. package/codegen/NativePerformanceSpec.g.h +1 -0
  93. package/codegen/NativePermissionsAndroidSpec.g.h +1 -0
  94. package/codegen/NativePlatformConstantsAndroidSpec.g.h +1 -0
  95. package/codegen/NativePlatformConstantsIOSSpec.g.h +1 -0
  96. package/codegen/NativePlatformConstantsWindowsSpec.g.h +1 -0
  97. package/codegen/NativePushNotificationManagerIOSSpec.g.h +1 -0
  98. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +1 -0
  99. package/codegen/NativeRedBoxSpec.g.h +1 -0
  100. package/codegen/NativeSampleTurboModuleSpec.g.h +1 -0
  101. package/codegen/NativeSegmentFetcherSpec.g.h +1 -0
  102. package/codegen/NativeSettingsManagerSpec.g.h +1 -0
  103. package/codegen/NativeShareModuleSpec.g.h +1 -0
  104. package/codegen/NativeSoundManagerSpec.g.h +1 -0
  105. package/codegen/NativeSourceCodeSpec.g.h +1 -0
  106. package/codegen/NativeStatusBarManagerAndroidSpec.g.h +1 -0
  107. package/codegen/NativeStatusBarManagerIOSSpec.g.h +1 -0
  108. package/codegen/NativeTimingSpec.g.h +1 -0
  109. package/codegen/NativeToastAndroidSpec.g.h +1 -0
  110. package/codegen/NativeUIManagerSpec.g.h +1 -0
  111. package/codegen/NativeVibrationSpec.g.h +1 -0
  112. package/codegen/NativeWebSocketModuleSpec.g.h +1 -0
  113. package/package.json +3 -3
@@ -0,0 +1,943 @@
1
+
2
+ // Copyright (c) Microsoft Corporation.
3
+ // Licensed under the MIT License.
4
+
5
+ #include "BorderPrimitive.h"
6
+
7
+ #include <AutoDraw.h>
8
+ #include "CompositionViewComponentView.h"
9
+
10
+ namespace winrt::Microsoft::ReactNative::Composition::implementation {
11
+
12
+ // Ideally isColorMeaningful would be sufficient here. But it appears to detect platformColors as not meaningful
13
+ // https://github.com/microsoft/react-native-windows/issues/14006
14
+ bool isColorMeaningful(
15
+ const facebook::react::SharedColor &color,
16
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme) noexcept {
17
+ if (!color) {
18
+ return false;
19
+ }
20
+
21
+ return theme->Color(*color).A > 0;
22
+ }
23
+
24
+ // We don't want half pixel borders, or border radii - they lead to blurry borders
25
+ // Also apply scale factor to the radii at this point
26
+ void pixelRoundBorderRadii(facebook::react::BorderRadii &borderRadii, float scaleFactor) noexcept {
27
+ // Always round radii down to avoid spikey circles
28
+ borderRadii.topLeft = {
29
+ std::floor(borderRadii.topLeft.horizontal * scaleFactor), std::floor(borderRadii.topLeft.vertical * scaleFactor)};
30
+ borderRadii.topRight = {
31
+ std::floor(borderRadii.topRight.horizontal * scaleFactor),
32
+ std::floor(borderRadii.topRight.vertical * scaleFactor)};
33
+ borderRadii.bottomLeft = {
34
+ std::floor(borderRadii.bottomLeft.horizontal * scaleFactor),
35
+ std::floor(borderRadii.bottomLeft.vertical * scaleFactor)};
36
+ borderRadii.bottomRight = {
37
+ std::floor(borderRadii.bottomRight.horizontal * scaleFactor),
38
+ std::floor(borderRadii.bottomRight.vertical * scaleFactor)};
39
+ }
40
+
41
+ void scaleAndPixelRoundBorderWidths(
42
+ facebook::react::LayoutMetrics const &layoutMetrics,
43
+ facebook::react::BorderMetrics &borderMetrics,
44
+ float scaleFactor) noexcept {
45
+ borderMetrics.borderWidths.left = (borderMetrics.borderWidths.left == 0)
46
+ ? 0.f
47
+ : std::max(1.f, std::round(borderMetrics.borderWidths.left * scaleFactor));
48
+ borderMetrics.borderWidths.top = (borderMetrics.borderWidths.top == 0)
49
+ ? 0.f
50
+ : std::max(1.f, std::round(borderMetrics.borderWidths.top * scaleFactor));
51
+ borderMetrics.borderWidths.right = (borderMetrics.borderWidths.right == 0)
52
+ ? 0.f
53
+ : std::max(1.f, std::round(borderMetrics.borderWidths.right * scaleFactor));
54
+ borderMetrics.borderWidths.bottom = (borderMetrics.borderWidths.bottom == 0)
55
+ ? 0.f
56
+ : std::max(1.f, std::round(borderMetrics.borderWidths.bottom * scaleFactor));
57
+
58
+ // If we rounded both sides of the borderWidths up, we may have made the borderWidths larger than the total
59
+ if (layoutMetrics.frame.size.width * scaleFactor <
60
+ (borderMetrics.borderWidths.left + borderMetrics.borderWidths.right)) {
61
+ borderMetrics.borderWidths.right--;
62
+ }
63
+ if (layoutMetrics.frame.size.height * scaleFactor <
64
+ (borderMetrics.borderWidths.top + borderMetrics.borderWidths.bottom)) {
65
+ borderMetrics.borderWidths.bottom--;
66
+ }
67
+ }
68
+
69
+ // react-native uses black as a default color when none is specified.
70
+ void assignDefaultBlackBorders(facebook::react::BorderMetrics &borderMetrics) noexcept {
71
+ if (!borderMetrics.borderColors.left) {
72
+ borderMetrics.borderColors.left = facebook::react::blackColor();
73
+ }
74
+ if (!borderMetrics.borderColors.top) {
75
+ borderMetrics.borderColors.top = facebook::react::blackColor();
76
+ }
77
+ if (!borderMetrics.borderColors.right) {
78
+ borderMetrics.borderColors.right = facebook::react::blackColor();
79
+ }
80
+ if (!borderMetrics.borderColors.bottom) {
81
+ borderMetrics.borderColors.bottom = facebook::react::blackColor();
82
+ }
83
+ }
84
+
85
+ facebook::react::BorderMetrics BorderPrimitive::resolveAndAlignBorderMetrics(
86
+ facebook::react::LayoutMetrics const &layoutMetrics,
87
+ const facebook::react::ViewProps &viewProps) noexcept {
88
+ auto borderMetrics = viewProps.resolveBorderMetrics(layoutMetrics);
89
+
90
+ pixelRoundBorderRadii(borderMetrics.borderRadii, layoutMetrics.pointScaleFactor);
91
+ scaleAndPixelRoundBorderWidths(layoutMetrics, borderMetrics, layoutMetrics.pointScaleFactor);
92
+ assignDefaultBlackBorders(borderMetrics);
93
+ return borderMetrics;
94
+ }
95
+
96
+ struct RoundedPathParameters {
97
+ float topLeftRadiusX = 0;
98
+ float topLeftRadiusY = 0;
99
+ float topRightRadiusX = 0;
100
+ float topRightRadiusY = 0;
101
+ float bottomRightRadiusX = 0;
102
+ float bottomRightRadiusY = 0;
103
+ float bottomLeftRadiusX = 0;
104
+ float bottomLeftRadiusY = 0;
105
+ };
106
+
107
+ /*
108
+ * Creates and returns a PathGeometry object used to clip the visuals of an element when a BorderRadius is set.
109
+ * Can also be used as part of a GeometryGroup for drawing a rounded border / innerstroke when called from
110
+ * GetGeometryForRoundedBorder. "params" defines the radii (horizontal and vertical) for each corner (top left, top
111
+ * right, bottom right, bottom left). "rectPathGeometry" defines the bounding box of the generated shape.
112
+ */
113
+ static winrt::com_ptr<ID2D1PathGeometry> GenerateRoundedRectPathGeometry(
114
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
115
+ const RoundedPathParameters &params,
116
+ const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
117
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry;
118
+ winrt::com_ptr<ID2D1Factory1> spD2dFactory;
119
+ compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
120
+ spD2dFactory.put());
121
+
122
+ // Create a path geometry.
123
+ HRESULT hr = spD2dFactory->CreatePathGeometry(pathGeometry.put());
124
+ if (FAILED(hr)) {
125
+ assert(false);
126
+ return nullptr;
127
+ }
128
+
129
+ // Write to the path geometry using the geometry sink.
130
+ winrt::com_ptr<ID2D1GeometrySink> spSink = nullptr;
131
+ hr = pathGeometry->Open(spSink.put());
132
+
133
+ if (FAILED(hr)) {
134
+ assert(false);
135
+ return nullptr;
136
+ }
137
+
138
+ float left = rectPathGeometry.left;
139
+ float right = rectPathGeometry.right;
140
+ float top = rectPathGeometry.top;
141
+ float bottom = rectPathGeometry.bottom;
142
+
143
+ // This function uses Cubic Beziers to approximate Arc segments, even though D2D supports arcs.
144
+ // This is INTENTIONAL. D2D Arc Segments are eventually converted into cubic beziers, but this
145
+ // is done in such a way that we don't have control over how many bezier curve segments are used
146
+ // for each arc. We need to ensure that we always use the same number of control points so that
147
+ // our paths can be used in a PathKeyFrameAnimation.
148
+ // Value for control point scale factor derived from methods described in:
149
+ // https://web.archive.org/web/20200322075504/http://itc.ktu.lt/index.php/ITC/article/download/11812/6479
150
+ constexpr float controlPointScaleFactor = 0.44771528244f; // 1 - (4 * (sqrtf(2.0f) - 1) / 3.0f);
151
+
152
+ #ifdef DEBUG
153
+ // std::sqrtf is not constexpr, so we precalculated this and wrote it in a constexpr form above.
154
+ // On debug, we should still check that the values are equivalent, though.
155
+ static float calculatedScaleFactor = 1 - (4 * (sqrtf(2.0f) - 1) / 3.0f);
156
+ assert(controlPointScaleFactor == calculatedScaleFactor);
157
+ #endif // DEBUG
158
+
159
+ bool needsConsistentNumberOfControlPoints = true; // VisualVersion::IsUseWinCompClippingRegionEnabled();
160
+
161
+ if (needsConsistentNumberOfControlPoints || (params.topLeftRadiusX != 0.0 && params.topLeftRadiusY != 0.0)) {
162
+ spSink->BeginFigure(D2D1::Point2F(left + params.topLeftRadiusX, top), D2D1_FIGURE_BEGIN_FILLED);
163
+ } else {
164
+ spSink->BeginFigure(D2D1::Point2F(left, top), D2D1_FIGURE_BEGIN_FILLED);
165
+ }
166
+
167
+ // Move to the top right corner
168
+ spSink->AddLine(D2D1::Point2F(right - params.topRightRadiusX, top));
169
+ if (needsConsistentNumberOfControlPoints) {
170
+ D2D1_BEZIER_SEGMENT arcSegmentTopRight = {
171
+ D2D1::Point2F(right - controlPointScaleFactor * params.topRightRadiusX, top),
172
+ D2D1::Point2F(right, top + controlPointScaleFactor * params.topRightRadiusY),
173
+ D2D1::Point2F(right, top + params.topRightRadiusY)};
174
+
175
+ spSink->AddBezier(&arcSegmentTopRight);
176
+ } else if (params.topRightRadiusX != 0.0 && params.topRightRadiusY != 0.0) {
177
+ D2D1_ARC_SEGMENT arcSegmentTopRight = {
178
+ D2D1::Point2F(right, top + params.topRightRadiusY),
179
+ D2D1::SizeF(params.topRightRadiusX, params.topRightRadiusY),
180
+ 0.0f,
181
+ D2D1_SWEEP_DIRECTION_CLOCKWISE,
182
+ D2D1_ARC_SIZE_SMALL};
183
+
184
+ spSink->AddArc(&arcSegmentTopRight);
185
+ } else {
186
+ spSink->AddLine(D2D1::Point2F(right, top));
187
+ }
188
+
189
+ // Move to the bottom right corner
190
+ spSink->AddLine(D2D1::Point2F(right, bottom - params.bottomRightRadiusY));
191
+ if (needsConsistentNumberOfControlPoints) {
192
+ D2D1_BEZIER_SEGMENT arcSegmentBottomRight = {
193
+ D2D1::Point2F(right, bottom - controlPointScaleFactor * params.bottomRightRadiusY),
194
+ D2D1::Point2F(right - controlPointScaleFactor * params.bottomRightRadiusX, bottom),
195
+ D2D1::Point2F(right - params.bottomRightRadiusX, bottom)};
196
+
197
+ spSink->AddBezier(&arcSegmentBottomRight);
198
+ } else if (params.bottomRightRadiusX != 0.0 && params.bottomRightRadiusY != 0.0) {
199
+ D2D1_ARC_SEGMENT arcSegmentBottomRight = {
200
+ D2D1::Point2F(right - params.bottomRightRadiusX, bottom),
201
+ D2D1::SizeF(params.bottomRightRadiusX, params.bottomRightRadiusY),
202
+ 0.0f,
203
+ D2D1_SWEEP_DIRECTION_CLOCKWISE,
204
+ D2D1_ARC_SIZE_SMALL};
205
+
206
+ spSink->AddArc(&arcSegmentBottomRight);
207
+ } else {
208
+ spSink->AddLine(D2D1::Point2F(right, bottom));
209
+ }
210
+
211
+ // Move to the bottom left corner
212
+ spSink->AddLine(D2D1::Point2F(left + params.bottomLeftRadiusX, bottom));
213
+ if (needsConsistentNumberOfControlPoints) {
214
+ D2D1_BEZIER_SEGMENT arcSegmentBottomLeft = {
215
+ D2D1::Point2F(left + controlPointScaleFactor * params.bottomLeftRadiusX, bottom),
216
+ D2D1::Point2F(left, bottom - controlPointScaleFactor * params.bottomLeftRadiusY),
217
+ D2D1::Point2F(left, bottom - params.bottomLeftRadiusY)};
218
+
219
+ spSink->AddBezier(&arcSegmentBottomLeft);
220
+ } else if (params.bottomLeftRadiusX != 0.0 && params.bottomLeftRadiusY != 0.0) {
221
+ D2D1_ARC_SEGMENT arcSegmentBottomLeft = {
222
+ D2D1::Point2F(left, bottom - params.bottomLeftRadiusY),
223
+ D2D1::SizeF(params.bottomLeftRadiusX, params.bottomLeftRadiusY),
224
+ 0.0f,
225
+ D2D1_SWEEP_DIRECTION_CLOCKWISE,
226
+ D2D1_ARC_SIZE_SMALL};
227
+
228
+ spSink->AddArc(&arcSegmentBottomLeft);
229
+ } else {
230
+ spSink->AddLine(D2D1::Point2F(left, bottom));
231
+ }
232
+
233
+ // Move to the top left corner
234
+ spSink->AddLine(D2D1::Point2F(left, top + params.topLeftRadiusY));
235
+ if (needsConsistentNumberOfControlPoints) {
236
+ D2D1_BEZIER_SEGMENT arcSegmentTopLeft = {
237
+ D2D1::Point2F(left, top + controlPointScaleFactor * params.topLeftRadiusY),
238
+ D2D1::Point2F(left + controlPointScaleFactor * params.topLeftRadiusX, top),
239
+ D2D1::Point2F(left + params.topLeftRadiusX, top)};
240
+
241
+ spSink->AddBezier(&arcSegmentTopLeft);
242
+ } else if (params.topLeftRadiusX != 0.0 && params.topLeftRadiusY != 0.0) {
243
+ D2D1_ARC_SEGMENT arcSegmentTopLeft = {
244
+ D2D1::Point2F(left + params.topLeftRadiusX, top),
245
+ D2D1::SizeF(params.topLeftRadiusX, params.topLeftRadiusY),
246
+ 0.0f,
247
+ D2D1_SWEEP_DIRECTION_CLOCKWISE,
248
+ D2D1_ARC_SIZE_SMALL};
249
+
250
+ spSink->AddArc(&arcSegmentTopLeft);
251
+ } else {
252
+ spSink->AddLine(D2D1::Point2F(left, top));
253
+ }
254
+
255
+ spSink->EndFigure(D2D1_FIGURE_END_CLOSED);
256
+ spSink->Close();
257
+
258
+ return pathGeometry;
259
+ }
260
+
261
+ RoundedPathParameters GenerateRoundedPathParameters(
262
+ const facebook::react::RectangleCorners<facebook::react::CornerRadii> &baseRadius,
263
+ const facebook::react::RectangleEdges<float> &inset,
264
+ const facebook::react::Size &pathSize) noexcept {
265
+ RoundedPathParameters result;
266
+
267
+ if (pathSize.width == 0 || pathSize.height == 0) {
268
+ return result;
269
+ }
270
+
271
+ float totalTopRadius = baseRadius.topLeft.horizontal + baseRadius.topRight.horizontal;
272
+ float totalRightRadius = baseRadius.topRight.vertical + baseRadius.bottomRight.vertical;
273
+ float totalBottomRadius = baseRadius.bottomRight.horizontal + baseRadius.bottomLeft.horizontal;
274
+ float totalLeftRadius = baseRadius.bottomLeft.vertical + baseRadius.topLeft.vertical;
275
+
276
+ float maxHorizontalRadius = std::max(totalTopRadius, totalBottomRadius);
277
+ float maxVerticalRadius = std::max(totalLeftRadius, totalRightRadius);
278
+
279
+ double totalWidth = inset.left + inset.right + pathSize.width;
280
+ double totalHeight = inset.top + inset.bottom + pathSize.height;
281
+
282
+ float scaleHoriz = static_cast<float>(maxHorizontalRadius / totalWidth);
283
+ float scaleVert = static_cast<float>(maxVerticalRadius / totalHeight);
284
+
285
+ float maxScale = std::max(1.0f, std::max(scaleHoriz, scaleVert));
286
+
287
+ result.topLeftRadiusX = std::max(0.0f, baseRadius.topLeft.horizontal / maxScale - inset.left);
288
+ result.topLeftRadiusY = std::max(0.0f, baseRadius.topLeft.vertical / maxScale - inset.top);
289
+ result.topRightRadiusX = std::max(0.0f, baseRadius.topRight.horizontal / maxScale - inset.right);
290
+ result.topRightRadiusY = std::max(0.0f, baseRadius.topRight.vertical / maxScale - inset.top);
291
+ result.bottomRightRadiusX = std::max(0.0f, baseRadius.bottomRight.horizontal / maxScale - inset.right);
292
+ result.bottomRightRadiusY = std::max(0.0f, baseRadius.bottomRight.vertical / maxScale - inset.bottom);
293
+ result.bottomLeftRadiusX = std::max(0.0f, baseRadius.bottomLeft.horizontal / maxScale - inset.left);
294
+ result.bottomLeftRadiusY = std::max(0.0f, baseRadius.bottomLeft.vertical / maxScale - inset.bottom);
295
+
296
+ return result;
297
+ }
298
+
299
+ winrt::com_ptr<ID2D1PathGeometry> BorderPrimitive::GenerateRoundedRectPathGeometry(
300
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
301
+ const facebook::react::RectangleCorners<facebook::react::CornerRadii> &baseRadius,
302
+ const facebook::react::RectangleEdges<float> &inset,
303
+ const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
304
+ RoundedPathParameters params = GenerateRoundedPathParameters(
305
+ baseRadius,
306
+ inset,
307
+ {rectPathGeometry.right - rectPathGeometry.left, rectPathGeometry.bottom - rectPathGeometry.top});
308
+
309
+ return winrt::Microsoft::ReactNative::Composition::implementation::GenerateRoundedRectPathGeometry(
310
+ compContext, params, rectPathGeometry);
311
+ }
312
+
313
+ void DrawShape(
314
+ ID2D1RenderTarget *pRT,
315
+ const D2D1_RECT_F &rect,
316
+ ID2D1Brush *brush,
317
+ FLOAT strokeWidth,
318
+ ID2D1StrokeStyle *strokeStyle) {
319
+ pRT->DrawRectangle(rect, brush, strokeWidth, strokeStyle);
320
+ }
321
+
322
+ void DrawShape(ID2D1RenderTarget *pRT, ID2D1GeometryGroup &geometry, ID2D1Brush *brush, FLOAT, ID2D1StrokeStyle *) {
323
+ pRT->FillGeometry(&geometry, brush);
324
+ }
325
+
326
+ void DrawShape(
327
+ ID2D1RenderTarget *pRT,
328
+ ID2D1PathGeometry &geometry,
329
+ ID2D1Brush *brush,
330
+ FLOAT strokeWidth,
331
+ ID2D1StrokeStyle *strokeStyle) {
332
+ pRT->DrawGeometry(&geometry, brush, strokeWidth, strokeStyle);
333
+ }
334
+
335
+ template <typename TShape>
336
+ void SetBorderLayerPropertiesCommon(
337
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
338
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
339
+ winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual &layer,
340
+ TShape &shape,
341
+ winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
342
+ &borderTexture,
343
+ const D2D1_RECT_F &textureRect,
344
+ facebook::react::Point anchorPoint,
345
+ facebook::react::Point anchorOffset,
346
+ winrt::Windows::Foundation::Numerics::float2 size,
347
+ winrt::Windows::Foundation::Numerics::float2 relativeSizeAdjustment,
348
+ FLOAT strokeWidth,
349
+ const facebook::react::SharedColor &borderColor,
350
+ facebook::react::BorderStyle borderStyle) {
351
+ layer.Offset({anchorOffset.x, anchorOffset.y, 0}, {anchorPoint.x, anchorPoint.y, 0});
352
+ layer.RelativeSizeWithOffset(size, relativeSizeAdjustment);
353
+ layer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
354
+
355
+ if ((textureRect.right - textureRect.left) <= 0 || (textureRect.bottom - textureRect.top) <= 0)
356
+ return;
357
+
358
+ auto surface = compContext.CreateDrawingSurfaceBrush(
359
+ {(textureRect.right - textureRect.left), (textureRect.bottom - textureRect.top)},
360
+ winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
361
+ winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);
362
+ surface.as(borderTexture);
363
+
364
+ layer.Brush(surface);
365
+
366
+ POINT offset;
367
+ ::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(
368
+ surface, 1.0f /* We have already done the dpi scaling */, &offset);
369
+ if (auto pRT = autoDraw.GetRenderTarget()) {
370
+ // Clear with transparency
371
+ pRT->Clear();
372
+
373
+ if (!isColorMeaningful(borderColor, theme)) {
374
+ return;
375
+ }
376
+
377
+ winrt::com_ptr<ID2D1Factory> spFactory;
378
+ pRT->GetFactory(spFactory.put());
379
+ assert(spFactory);
380
+ if (spFactory == nullptr)
381
+ return;
382
+
383
+ winrt::com_ptr<ID2D1SolidColorBrush> spBorderBrush;
384
+ pRT->CreateSolidColorBrush(theme->D2DColor(*borderColor), spBorderBrush.put());
385
+ assert(spBorderBrush);
386
+ if (spBorderBrush == nullptr)
387
+ return;
388
+
389
+ winrt::com_ptr<ID2D1StrokeStyle> spStrokeStyle;
390
+
391
+ enum class BorderStyle { Solid, Dotted, Dashed };
392
+
393
+ if (borderStyle == facebook::react::BorderStyle::Dotted || borderStyle == facebook::react::BorderStyle::Dashed) {
394
+ const auto capStyle =
395
+ borderStyle == facebook::react::BorderStyle::Dashed ? D2D1_CAP_STYLE_FLAT : D2D1_CAP_STYLE_ROUND;
396
+ const auto strokeStyleProps = D2D1::StrokeStyleProperties(
397
+ capStyle,
398
+ capStyle,
399
+ capStyle,
400
+ D2D1_LINE_JOIN_MITER,
401
+ 10.0f,
402
+ borderStyle == facebook::react::BorderStyle::Dashed ? D2D1_DASH_STYLE_DASH : D2D1_DASH_STYLE_DOT,
403
+ 0.0f);
404
+ spFactory->CreateStrokeStyle(&strokeStyleProps, nullptr, 0, spStrokeStyle.put());
405
+ }
406
+ D2D1::Matrix3x2F originalTransform;
407
+ D2D1::Matrix3x2F translationTransform =
408
+ D2D1::Matrix3x2F::Translation(-textureRect.left + offset.x, -textureRect.top + offset.y);
409
+
410
+ pRT->GetTransform(&originalTransform);
411
+ translationTransform = originalTransform * translationTransform;
412
+
413
+ pRT->SetTransform(translationTransform);
414
+
415
+ DrawShape(pRT, shape, spBorderBrush.get(), strokeWidth, spStrokeStyle.get());
416
+
417
+ pRT->SetTransform(originalTransform);
418
+ }
419
+ }
420
+
421
+ template <typename TShape>
422
+ void SetBorderLayerProperties(
423
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
424
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
425
+ winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual &layer,
426
+ TShape &shape,
427
+ winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
428
+ &borderTexture,
429
+ const D2D1_RECT_F &textureRect,
430
+ facebook::react::Point anchorPoint,
431
+ facebook::react::Point anchorOffset,
432
+ winrt::Windows::Foundation::Numerics::float2 size,
433
+ winrt::Windows::Foundation::Numerics::float2 relativeSizeAdjustment,
434
+ FLOAT strokeWidth,
435
+ const facebook::react::SharedColor &borderColor,
436
+ facebook::react::BorderStyle borderStyle) {
437
+ if constexpr (!std::is_base_of_v<ID2D1GeometryGroup, TShape>) {
438
+ SetBorderLayerPropertiesCommon(
439
+ theme,
440
+ compContext,
441
+ layer,
442
+ shape,
443
+ borderTexture,
444
+ textureRect,
445
+ anchorPoint,
446
+ anchorOffset,
447
+ size,
448
+ relativeSizeAdjustment,
449
+ strokeWidth,
450
+ borderColor,
451
+ borderStyle);
452
+ } else {
453
+ // if (VisualVersion::IsUseWinCompClippingRegionEnabled())
454
+ {
455
+ layer.Offset({anchorOffset.x, anchorOffset.y, 0}, {anchorPoint.x, anchorPoint.y, 0});
456
+ layer.RelativeSizeWithOffset(
457
+ {textureRect.right - textureRect.left, textureRect.bottom - textureRect.top}, {0.0f, 0.0f});
458
+
459
+ layer.Brush(theme->Brush(*borderColor));
460
+
461
+ winrt::com_ptr<ID2D1Factory1> spD2dFactory;
462
+ compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
463
+ spD2dFactory.put());
464
+
465
+ winrt::com_ptr<ID2D1TransformedGeometry> transformedShape;
466
+ D2D1::Matrix3x2F translationTransform = D2D1::Matrix3x2F::Translation(-textureRect.left, -textureRect.top);
467
+ winrt::check_hresult(
468
+ spD2dFactory->CreateTransformedGeometry(&shape, &translationTransform, transformedShape.put()));
469
+
470
+ layer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
471
+ transformedShape.get());
472
+ }
473
+ /*
474
+ else
475
+ {
476
+ SetBorderLayerPropertiesCommon(theme, comContext, layer, shape, borderTexture, textureRect,
477
+ anchorPoint, anchorOffset, strokeWidth, borderColor, borderStyle);
478
+ }
479
+ */
480
+ }
481
+ }
482
+
483
+ namespace AnchorPosition {
484
+ const float Left = 0.0;
485
+ const float Center = 0.5;
486
+ const float Right = 1.0;
487
+ const float Top = 0.0;
488
+ const float Bottom = 1.0;
489
+ } // namespace AnchorPosition
490
+
491
+ template <typename TShape>
492
+ void DrawAllBorderLayers(
493
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
494
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
495
+ std::array<
496
+ winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual,
497
+ BorderPrimitive::SpecialBorderLayerCount> &spBorderLayers,
498
+ TShape &shape,
499
+ const facebook::react::BorderWidths &borderWidths,
500
+ const facebook::react::BorderRadii &borderRadii,
501
+ float textureWidth,
502
+ float textureHeight,
503
+ const facebook::react::BorderColors &borderColors,
504
+ facebook::react::BorderStyle borderStyle) {
505
+ // Now that we've drawn our nice border in one layer, split it into its component layers
506
+ winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
507
+ spTextures[BorderPrimitive::SpecialBorderLayerCount];
508
+
509
+ // Set component border properties
510
+ // Top Left Corner
511
+ SetBorderLayerProperties(
512
+ theme,
513
+ compContext,
514
+ spBorderLayers[0],
515
+ shape,
516
+ spTextures[0], // Target Layer, Source Texture, Target Texture
517
+ {0,
518
+ 0,
519
+ borderRadii.topLeft.vertical + borderWidths.left,
520
+ borderRadii.topLeft.horizontal + borderWidths.top}, // Texture Left, Top, Width, Height
521
+ {AnchorPosition::Left, AnchorPosition::Top}, // Layer Anchor Point
522
+ {0, 0}, // Layer Anchor Offset
523
+ {borderRadii.topLeft.vertical + borderWidths.left, borderRadii.topLeft.horizontal + borderWidths.top}, // size
524
+ {0.0f, 0.0f}, // relativeSize
525
+ std::max(borderWidths.left, borderWidths.top),
526
+ borderColors.left ? borderColors.left : borderColors.top,
527
+ borderStyle);
528
+
529
+ // Top Edge Border
530
+ SetBorderLayerProperties(
531
+ theme,
532
+ compContext,
533
+ spBorderLayers[1],
534
+ shape,
535
+ spTextures[1],
536
+ {borderRadii.topLeft.vertical + borderWidths.left,
537
+ 0,
538
+ textureWidth - (borderRadii.topRight.vertical + borderWidths.right),
539
+ borderWidths.top},
540
+ {AnchorPosition::Left, AnchorPosition::Top},
541
+ {borderRadii.topLeft.vertical + borderWidths.left, 0},
542
+ {-(borderRadii.topLeft.vertical + borderWidths.left + borderRadii.topRight.vertical + borderWidths.right),
543
+ borderWidths.top}, // size
544
+ {1.0f, 0.0f}, // relativeSize
545
+ borderWidths.top,
546
+ borderColors.top,
547
+ borderStyle);
548
+
549
+ // Top Right Corner Border
550
+ SetBorderLayerProperties(
551
+ theme,
552
+ compContext,
553
+ spBorderLayers[2],
554
+ shape,
555
+ spTextures[2],
556
+ {textureWidth - (borderRadii.topRight.vertical + borderWidths.right),
557
+ 0,
558
+ textureWidth,
559
+ borderRadii.topRight.horizontal + borderWidths.top},
560
+ {AnchorPosition::Right, AnchorPosition::Top},
561
+ {-(borderRadii.topRight.vertical + borderWidths.right), 0},
562
+ {borderRadii.topRight.vertical + borderWidths.right, borderRadii.topRight.horizontal + borderWidths.top},
563
+ {0.0f, 0.0f},
564
+ std::max(borderWidths.right, borderWidths.top),
565
+ borderColors.right ? borderColors.right : borderColors.top,
566
+ borderStyle);
567
+
568
+ // Right Edge Border
569
+ SetBorderLayerProperties(
570
+ theme,
571
+ compContext,
572
+ spBorderLayers[3],
573
+ shape,
574
+ spTextures[3],
575
+ {textureWidth - borderWidths.right,
576
+ borderWidths.top + borderRadii.topRight.horizontal,
577
+ textureWidth,
578
+ textureHeight - (borderWidths.bottom + borderRadii.bottomRight.horizontal)},
579
+ {AnchorPosition::Right, AnchorPosition::Top},
580
+ {-borderWidths.right, borderWidths.top + borderRadii.topRight.horizontal},
581
+ {borderWidths.right,
582
+ -(borderWidths.top + borderRadii.topRight.horizontal + borderWidths.bottom +
583
+ borderRadii.bottomRight.horizontal)}, // size
584
+ {0.0f, 1.0f},
585
+ borderWidths.right,
586
+ borderColors.right,
587
+ borderStyle);
588
+
589
+ // Bottom Right Corner Border
590
+ SetBorderLayerProperties(
591
+ theme,
592
+ compContext,
593
+ spBorderLayers[4],
594
+ shape,
595
+ spTextures[4],
596
+ {textureWidth - (borderWidths.right + borderRadii.bottomRight.vertical),
597
+ textureHeight - (borderWidths.bottom + borderRadii.bottomRight.horizontal),
598
+ textureWidth,
599
+ textureHeight},
600
+ {AnchorPosition::Right, AnchorPosition::Bottom},
601
+ {-(borderWidths.right + borderRadii.bottomRight.vertical),
602
+ -(borderWidths.bottom + borderRadii.bottomRight.horizontal)},
603
+ {borderWidths.right + borderRadii.bottomRight.vertical, borderWidths.bottom + borderRadii.bottomRight.horizontal},
604
+ {0, 0},
605
+ std::max(borderWidths.right, borderWidths.bottom),
606
+ borderColors.right ? borderColors.right : borderColors.bottom,
607
+ borderStyle);
608
+
609
+ // Bottom Edge Border
610
+ SetBorderLayerProperties(
611
+ theme,
612
+ compContext,
613
+ spBorderLayers[5],
614
+ shape,
615
+ spTextures[5],
616
+ {borderWidths.left + borderRadii.bottomLeft.vertical,
617
+ textureHeight - borderWidths.bottom,
618
+ textureWidth - (borderWidths.right + borderRadii.bottomRight.vertical),
619
+ textureHeight},
620
+ {AnchorPosition::Left, AnchorPosition::Bottom},
621
+ {borderWidths.left + borderRadii.bottomLeft.vertical, -borderWidths.bottom},
622
+ {-(borderWidths.right + borderRadii.bottomLeft.vertical + borderWidths.left + borderRadii.bottomRight.vertical),
623
+ borderWidths.bottom},
624
+ {1.0f, 0.0f},
625
+ borderWidths.bottom,
626
+ borderColors.bottom,
627
+ borderStyle);
628
+
629
+ // Bottom Left Corner Border
630
+ SetBorderLayerProperties(
631
+ theme,
632
+ compContext,
633
+ spBorderLayers[6],
634
+ shape,
635
+ spTextures[6],
636
+ {0,
637
+ textureHeight - (borderWidths.bottom + borderRadii.bottomLeft.horizontal),
638
+ borderWidths.left + borderRadii.bottomLeft.vertical,
639
+ textureHeight},
640
+ {AnchorPosition::Left, AnchorPosition::Bottom},
641
+ {0, -(borderWidths.bottom + borderRadii.bottomLeft.horizontal)},
642
+ {borderWidths.left + borderRadii.bottomLeft.vertical, borderWidths.bottom + borderRadii.bottomLeft.horizontal},
643
+ {0, 0},
644
+ std::max(borderWidths.left, borderWidths.bottom),
645
+ borderColors.left ? borderColors.left : borderColors.bottom,
646
+ borderStyle);
647
+
648
+ // Left Edge Border
649
+ SetBorderLayerProperties(
650
+ theme,
651
+ compContext,
652
+ spBorderLayers[7],
653
+ shape,
654
+ spTextures[7],
655
+ {0,
656
+ borderWidths.top + borderRadii.topLeft.horizontal,
657
+ borderWidths.left,
658
+ textureHeight - (borderWidths.bottom + borderRadii.bottomLeft.horizontal)},
659
+ {AnchorPosition::Left, AnchorPosition::Top},
660
+ {0, borderWidths.top + borderRadii.topLeft.horizontal},
661
+ {borderWidths.left,
662
+ -(borderWidths.top + borderRadii.topLeft.horizontal + borderWidths.bottom + borderRadii.bottomLeft.horizontal)},
663
+ {0, 1},
664
+ borderWidths.left,
665
+ borderColors.left,
666
+ borderStyle);
667
+ }
668
+
669
+ winrt::com_ptr<ID2D1GeometryGroup> GetGeometryForRoundedBorder(
670
+ winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
671
+ const facebook::react::RectangleCorners<facebook::react::CornerRadii> &radius,
672
+ const facebook::react::RectangleEdges<float> &inset,
673
+ const facebook::react::RectangleEdges<float> &thickness,
674
+ const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
675
+ winrt::com_ptr<ID2D1PathGeometry> outerPathGeometry =
676
+ BorderPrimitive::GenerateRoundedRectPathGeometry(compContext, radius, inset, rectPathGeometry);
677
+
678
+ if (outerPathGeometry == nullptr) {
679
+ assert(false);
680
+ return nullptr;
681
+ }
682
+
683
+ facebook::react::RectangleEdges<float> rectInnerPathGeometry = {
684
+ rectPathGeometry.left + thickness.left,
685
+ rectPathGeometry.top + thickness.top,
686
+ rectPathGeometry.right - thickness.right,
687
+ rectPathGeometry.bottom - thickness.bottom};
688
+
689
+ // Total thickness is larger than original element size.
690
+ // Clamp inner rect to have a width/height of 0, but placed such that the ratio of side-thicknesses is respected.
691
+ // We need to respect this ratio so that any animations work properly.
692
+
693
+ if (rectInnerPathGeometry.left > rectInnerPathGeometry.right) {
694
+ float leftRatio = thickness.left / (thickness.left + thickness.right);
695
+ auto x = std::floor(rectPathGeometry.left + ((rectPathGeometry.right - rectPathGeometry.left) * leftRatio));
696
+ rectInnerPathGeometry.left = x;
697
+ rectInnerPathGeometry.right = x;
698
+ }
699
+
700
+ if (rectInnerPathGeometry.top > rectInnerPathGeometry.bottom) {
701
+ float topRatio = thickness.top / (thickness.top + thickness.bottom);
702
+ auto y = rectPathGeometry.top + std::floor((rectPathGeometry.top - rectPathGeometry.bottom) * topRatio);
703
+ rectInnerPathGeometry.top = y;
704
+ rectInnerPathGeometry.bottom = y;
705
+ }
706
+
707
+ facebook::react::RectangleEdges<float> innerInset = {
708
+ inset.left + thickness.left,
709
+ inset.top + thickness.top,
710
+ inset.right + thickness.right,
711
+ inset.bottom + thickness.bottom};
712
+
713
+ winrt::com_ptr<ID2D1PathGeometry> innerPathGeometry =
714
+ BorderPrimitive::GenerateRoundedRectPathGeometry(compContext, radius, innerInset, rectInnerPathGeometry);
715
+
716
+ if (innerPathGeometry == nullptr) {
717
+ assert(false); // Failed to create inner pathGeometry for rounded border
718
+ return nullptr;
719
+ }
720
+
721
+ ID2D1Geometry *ppGeometries[] = {outerPathGeometry.get(), innerPathGeometry.get()};
722
+ winrt::com_ptr<ID2D1Factory1> spD2dFactory;
723
+ compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
724
+ spD2dFactory.put());
725
+
726
+ winrt::com_ptr<ID2D1GeometryGroup> geometryGroup = nullptr;
727
+ // Create a geometry group.
728
+ HRESULT hr = spD2dFactory->CreateGeometryGroup(
729
+ D2D1_FILL_MODE_ALTERNATE, ppGeometries, ARRAYSIZE(ppGeometries), geometryGroup.put());
730
+
731
+ if (SUCCEEDED(hr)) {
732
+ return geometryGroup;
733
+ }
734
+ return nullptr;
735
+ }
736
+
737
+ BorderPrimitive::BorderPrimitive(
738
+ winrt::Microsoft::ReactNative::Composition::implementation::ComponentView &outer,
739
+ const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &rootVisual)
740
+ : m_outer(&outer), m_rootVisual(rootVisual) {}
741
+
742
+ BorderPrimitive::BorderPrimitive(winrt::Microsoft::ReactNative::Composition::implementation::ComponentView &outer)
743
+ : m_outer(&outer), m_rootVisual(outer.CompositionContext().CreateSpriteVisual()) {}
744
+
745
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual BorderPrimitive::RootVisual() const noexcept {
746
+ return m_rootVisual;
747
+ }
748
+
749
+ bool BorderPrimitive::requiresBorder(
750
+ const facebook::react::BorderMetrics &borderMetrics,
751
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme) noexcept {
752
+ // We only handle a single borderStyle for now
753
+ auto borderStyle = borderMetrics.borderStyles.left;
754
+
755
+ bool hasMeaningfulColor =
756
+ !borderMetrics.borderColors.isUniform() || !isColorMeaningful(borderMetrics.borderColors.left, theme);
757
+ bool hasMeaningfulWidth = !borderMetrics.borderWidths.isUniform() || (borderMetrics.borderWidths.left != 0);
758
+ if (!hasMeaningfulColor && !hasMeaningfulWidth) {
759
+ return false;
760
+ }
761
+ return true;
762
+ }
763
+
764
+ void BorderPrimitive::updateProps(
765
+ const facebook::react::ViewProps &oldViewProps,
766
+ const facebook::react::ViewProps &newViewProps) noexcept {
767
+ if (oldViewProps.borderColors != newViewProps.borderColors || oldViewProps.borderRadii != newViewProps.borderRadii ||
768
+ !(oldViewProps.yogaStyle.border(facebook::yoga::Edge::All) ==
769
+ newViewProps.yogaStyle.border(facebook::yoga::Edge::All)) ||
770
+ oldViewProps.borderStyles != newViewProps.borderStyles) {
771
+ m_needsUpdate = true;
772
+ }
773
+ }
774
+
775
+ void BorderPrimitive::markNeedsUpdate() noexcept {
776
+ m_needsUpdate = true;
777
+ }
778
+
779
+ void BorderPrimitive::finalize(
780
+ facebook::react::LayoutMetrics const &layoutMetrics,
781
+ const facebook::react::BorderMetrics &borderMetrics) noexcept {
782
+ if (!m_needsUpdate) {
783
+ return;
784
+ }
785
+
786
+ auto theme = m_outer->theme();
787
+ if (!theme || theme->IsEmpty()) {
788
+ return;
789
+ }
790
+
791
+ m_needsUpdate = false;
792
+ auto spBorderLayers = FindSpecialBorderLayers();
793
+
794
+ if (!TryUpdateSpecialBorderLayers(m_outer->theme(), spBorderLayers, layoutMetrics, borderMetrics)) {
795
+ for (auto &spBorderLayer : spBorderLayers) {
796
+ if (spBorderLayer) {
797
+ spBorderLayer.as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Brush(nullptr);
798
+ }
799
+ }
800
+ }
801
+ }
802
+
803
+ void BorderPrimitive::onThemeChanged(
804
+ facebook::react::LayoutMetrics const &layoutMetrics,
805
+ const facebook::react::BorderMetrics &borderMetrics) noexcept {
806
+ m_needsUpdate = true;
807
+ finalize(layoutMetrics, borderMetrics);
808
+ }
809
+
810
+ std::array<
811
+ winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual,
812
+ BorderPrimitive::SpecialBorderLayerCount>
813
+ BorderPrimitive::FindSpecialBorderLayers() const noexcept {
814
+ std::array<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual, SpecialBorderLayerCount> layers{
815
+ nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
816
+
817
+ if (m_numBorderVisuals) {
818
+ for (uint8_t i = 0; i < m_numBorderVisuals; i++) {
819
+ auto visual = m_rootVisual.GetAt(i);
820
+ layers[i] = visual.as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>();
821
+ }
822
+ }
823
+
824
+ return layers;
825
+ }
826
+
827
+ uint8_t BorderPrimitive::numberOfVisuals() const noexcept {
828
+ return m_numBorderVisuals;
829
+ }
830
+
831
+ void BorderPrimitive::setOuter(
832
+ winrt::Microsoft::ReactNative::Composition::implementation::ComponentView *outer) noexcept {
833
+ m_outer = outer;
834
+ }
835
+
836
+ bool BorderPrimitive::TryUpdateSpecialBorderLayers(
837
+ winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
838
+ std::array<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual, SpecialBorderLayerCount>
839
+ &spBorderVisuals,
840
+ facebook::react::LayoutMetrics const &layoutMetrics,
841
+ const facebook::react::BorderMetrics &borderMetrics) noexcept {
842
+ // We only handle a single borderStyle for now
843
+ auto borderStyle = borderMetrics.borderStyles.left;
844
+
845
+ bool hasMeaningfulColor =
846
+ !borderMetrics.borderColors.isUniform() || !isColorMeaningful(borderMetrics.borderColors.left, theme);
847
+ bool hasMeaningfulWidth = !borderMetrics.borderWidths.isUniform() || (borderMetrics.borderWidths.left != 0);
848
+ if (!hasMeaningfulColor && !hasMeaningfulWidth) {
849
+ return false;
850
+ }
851
+
852
+ // Create the special border layers if they don't exist yet
853
+ if (!spBorderVisuals[0]) {
854
+ for (uint8_t i = 0; i < SpecialBorderLayerCount; i++) {
855
+ auto visual = m_outer->CompositionContext().CreateSpriteVisual();
856
+ m_rootVisual.InsertAt(visual, i);
857
+ spBorderVisuals[i] = std::move(visual);
858
+ m_numBorderVisuals++;
859
+ }
860
+ }
861
+
862
+ float extentWidth = layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor;
863
+ float extentHeight = layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor;
864
+
865
+ if (borderMetrics.borderRadii.topLeft.horizontal != 0 || borderMetrics.borderRadii.topRight.horizontal != 0 ||
866
+ borderMetrics.borderRadii.bottomLeft.horizontal != 0 || borderMetrics.borderRadii.bottomRight.horizontal != 0 ||
867
+ borderMetrics.borderRadii.topLeft.vertical != 0 || borderMetrics.borderRadii.topRight.vertical != 0 ||
868
+ borderMetrics.borderRadii.bottomLeft.vertical != 0 || borderMetrics.borderRadii.bottomRight.vertical != 0) {
869
+ auto compContext = m_outer->CompositionContext();
870
+ if (borderStyle == facebook::react::BorderStyle::Dotted || borderStyle == facebook::react::BorderStyle::Dashed) {
871
+ // Because in DirectX geometry starts at the center of the stroke, we need to deflate
872
+ // rectangle by half the stroke width to render correctly.
873
+ facebook::react::RectangleEdges<float> rectPathGeometry = {
874
+ borderMetrics.borderWidths.left / 2.0f,
875
+ borderMetrics.borderWidths.top / 2.0f,
876
+ extentWidth - borderMetrics.borderWidths.right / 2.0f,
877
+ extentHeight - borderMetrics.borderWidths.bottom / 2.0f};
878
+
879
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry =
880
+ GenerateRoundedRectPathGeometry(compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, rectPathGeometry);
881
+
882
+ if (pathGeometry) {
883
+ DrawAllBorderLayers(
884
+ theme,
885
+ compContext,
886
+ spBorderVisuals,
887
+ *pathGeometry,
888
+ borderMetrics.borderWidths,
889
+ borderMetrics.borderRadii,
890
+ extentWidth,
891
+ extentHeight,
892
+ borderMetrics.borderColors,
893
+ borderStyle);
894
+ } else {
895
+ assert(false);
896
+ }
897
+ } else {
898
+ facebook::react::RectangleEdges<float> rectPathGeometry = {0, 0, extentWidth, extentHeight};
899
+
900
+ winrt::com_ptr<ID2D1GeometryGroup> pathGeometry = GetGeometryForRoundedBorder(
901
+ compContext,
902
+ borderMetrics.borderRadii,
903
+ {0, 0, 0, 0}, // inset
904
+ borderMetrics.borderWidths,
905
+ rectPathGeometry);
906
+
907
+ DrawAllBorderLayers(
908
+ theme,
909
+ compContext,
910
+ spBorderVisuals,
911
+ *pathGeometry,
912
+ borderMetrics.borderWidths,
913
+ borderMetrics.borderRadii,
914
+ extentWidth,
915
+ extentHeight,
916
+ borderMetrics.borderColors,
917
+ borderStyle);
918
+ }
919
+ } else {
920
+ auto compContext = m_outer->CompositionContext();
921
+ // Because in DirectX geometry starts at the center of the stroke, we need to deflate rectangle by half the stroke
922
+ // width / height to render correctly.
923
+ D2D1_RECT_F rectShape{
924
+ borderMetrics.borderWidths.left / 2.0f,
925
+ borderMetrics.borderWidths.top / 2.0f,
926
+ extentWidth - (borderMetrics.borderWidths.right / 2.0f),
927
+ extentHeight - (borderMetrics.borderWidths.bottom / 2.0f)};
928
+ DrawAllBorderLayers(
929
+ theme,
930
+ compContext,
931
+ spBorderVisuals,
932
+ rectShape,
933
+ borderMetrics.borderWidths,
934
+ borderMetrics.borderRadii,
935
+ extentWidth,
936
+ extentHeight,
937
+ borderMetrics.borderColors,
938
+ borderStyle);
939
+ }
940
+ return true;
941
+ }
942
+
943
+ } // namespace winrt::Microsoft::ReactNative::Composition::implementation