react-native-windows 0.75.9 → 0.75.11

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 (118) hide show
  1. package/Libraries/Modal/Modal.windows.js +352 -0
  2. package/Microsoft.ReactNative/CompositionComponentView.idl +2 -1
  3. package/Microsoft.ReactNative/Fabric/AbiComponentDescriptor.cpp +4 -1
  4. package/Microsoft.ReactNative/Fabric/AbiViewComponentDescriptor.cpp +1 -1
  5. package/Microsoft.ReactNative/Fabric/AbiViewProps.cpp +7 -0
  6. package/Microsoft.ReactNative/Fabric/AbiViewProps.h +2 -0
  7. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +45 -50
  8. package/Microsoft.ReactNative/Fabric/ComponentView.h +14 -22
  9. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +931 -0
  10. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +80 -0
  11. package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +31 -13
  12. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +187 -6
  13. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +10 -1
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +8 -32
  15. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +316 -911
  16. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +32 -29
  17. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +9 -2
  18. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +2 -1
  19. package/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp +1 -1
  20. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +181 -123
  21. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +16 -8
  22. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +99 -37
  23. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +25 -3
  24. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +63 -2
  25. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +12 -0
  26. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +51 -3
  27. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +3 -0
  28. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +18 -8
  29. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +3 -0
  30. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +54 -5
  31. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +8 -0
  32. package/Microsoft.ReactNative/Fabric/Composition/Theme.cpp +9 -3
  33. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +11 -0
  34. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +2 -0
  35. package/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +1 -1
  36. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +26 -0
  37. package/Microsoft.ReactNative/IReactViewComponentBuilder.idl +1 -1
  38. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +25 -129
  39. package/Microsoft.ReactNative/ReactNativeAppBuilder.h +5 -13
  40. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +13 -34
  41. package/Microsoft.ReactNative/ReactNativeIsland.idl +3 -2
  42. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +129 -18
  43. package/Microsoft.ReactNative/ReactNativeWin32App.h +14 -5
  44. package/Microsoft.ReactNative/ViewProps.idl +2 -0
  45. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +14 -0
  46. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  47. package/Shared/Shared.vcxitems +3 -10
  48. package/Shared/Shared.vcxitems.filters +1 -0
  49. package/codegen/NativeAccessibilityInfoSpec.g.h +1 -0
  50. package/codegen/NativeAccessibilityManagerSpec.g.h +1 -0
  51. package/codegen/NativeActionSheetManagerSpec.g.h +1 -0
  52. package/codegen/NativeAlertManagerSpec.g.h +1 -0
  53. package/codegen/NativeAnimatedModuleSpec.g.h +1 -0
  54. package/codegen/NativeAnimatedTurboModuleSpec.g.h +1 -0
  55. package/codegen/NativeAppStateSpec.g.h +1 -0
  56. package/codegen/NativeAppThemeSpec.g.h +1 -0
  57. package/codegen/NativeAppearanceSpec.g.h +1 -0
  58. package/codegen/NativeBlobModuleSpec.g.h +1 -0
  59. package/codegen/NativeBugReportingSpec.g.h +1 -0
  60. package/codegen/NativeClipboardSpec.g.h +1 -0
  61. package/codegen/NativeDOMSpec.g.h +1 -0
  62. package/codegen/NativeDevLoadingViewSpec.g.h +1 -0
  63. package/codegen/NativeDevMenuSpec.g.h +1 -0
  64. package/codegen/NativeDevSettingsSpec.g.h +1 -0
  65. package/codegen/NativeDevToolsSettingsManagerSpec.g.h +1 -0
  66. package/codegen/NativeDeviceEventManagerSpec.g.h +1 -0
  67. package/codegen/NativeDeviceInfoSpec.g.h +1 -0
  68. package/codegen/NativeDialogManagerAndroidSpec.g.h +1 -0
  69. package/codegen/NativeDialogManagerWindowsSpec.g.h +1 -0
  70. package/codegen/NativeExceptionsManagerSpec.g.h +1 -0
  71. package/codegen/NativeFileReaderModuleSpec.g.h +1 -0
  72. package/codegen/NativeFrameRateLoggerSpec.g.h +1 -0
  73. package/codegen/NativeHeadlessJsTaskSupportSpec.g.h +1 -0
  74. package/codegen/NativeI18nManagerSpec.g.h +1 -0
  75. package/codegen/NativeIdleCallbacksSpec.g.h +1 -0
  76. package/codegen/NativeImageEditorSpec.g.h +1 -0
  77. package/codegen/NativeImageLoaderAndroidSpec.g.h +1 -0
  78. package/codegen/NativeImageLoaderIOSSpec.g.h +1 -0
  79. package/codegen/NativeImageStoreAndroidSpec.g.h +1 -0
  80. package/codegen/NativeImageStoreIOSSpec.g.h +1 -0
  81. package/codegen/NativeIntentAndroidSpec.g.h +1 -0
  82. package/codegen/NativeIntersectionObserverSpec.g.h +1 -0
  83. package/codegen/NativeJSCHeapCaptureSpec.g.h +1 -0
  84. package/codegen/NativeJSCSamplingProfilerSpec.g.h +1 -0
  85. package/codegen/NativeKeyboardObserverSpec.g.h +1 -0
  86. package/codegen/NativeLinkingManagerSpec.g.h +1 -0
  87. package/codegen/NativeLogBoxSpec.g.h +1 -0
  88. package/codegen/NativeMicrotasksSpec.g.h +1 -0
  89. package/codegen/NativeModalManagerSpec.g.h +1 -0
  90. package/codegen/NativeMutationObserverSpec.g.h +1 -0
  91. package/codegen/NativeNetworkingAndroidSpec.g.h +1 -0
  92. package/codegen/NativeNetworkingIOSSpec.g.h +1 -0
  93. package/codegen/NativePerformanceObserverSpec.g.h +1 -0
  94. package/codegen/NativePerformanceSpec.g.h +1 -0
  95. package/codegen/NativePermissionsAndroidSpec.g.h +1 -0
  96. package/codegen/NativePlatformConstantsAndroidSpec.g.h +1 -0
  97. package/codegen/NativePlatformConstantsIOSSpec.g.h +1 -0
  98. package/codegen/NativePlatformConstantsWindowsSpec.g.h +1 -0
  99. package/codegen/NativePushNotificationManagerIOSSpec.g.h +1 -0
  100. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +1 -0
  101. package/codegen/NativeRedBoxSpec.g.h +1 -0
  102. package/codegen/NativeSampleTurboModuleSpec.g.h +1 -0
  103. package/codegen/NativeSegmentFetcherSpec.g.h +1 -0
  104. package/codegen/NativeSettingsManagerSpec.g.h +1 -0
  105. package/codegen/NativeShareModuleSpec.g.h +1 -0
  106. package/codegen/NativeSoundManagerSpec.g.h +1 -0
  107. package/codegen/NativeSourceCodeSpec.g.h +1 -0
  108. package/codegen/NativeStatusBarManagerAndroidSpec.g.h +1 -0
  109. package/codegen/NativeStatusBarManagerIOSSpec.g.h +1 -0
  110. package/codegen/NativeTimingSpec.g.h +1 -0
  111. package/codegen/NativeToastAndroidSpec.g.h +1 -0
  112. package/codegen/NativeUIManagerSpec.g.h +1 -0
  113. package/codegen/NativeVibrationSpec.g.h +1 -0
  114. package/codegen/NativeWebSocketModuleSpec.g.h +1 -0
  115. package/package.json +3 -3
  116. package/templates/cpp-app/windows/MyApp/MyApp.cpp +46 -130
  117. package/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp +0 -59
  118. package/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h +0 -23
@@ -32,16 +32,32 @@
32
32
 
33
33
  namespace winrt::Microsoft::ReactNative::Composition::implementation {
34
34
 
35
+ constexpr float FOCUS_VISUAL_WIDTH = 2.0f;
36
+
37
+ // m_outerVisual
38
+ // |
39
+ // |
40
+ // ----- m_visual <-- Background / clip - Can be a custom visual depending on Component type
41
+ // |
42
+ // ----- Border Visuals x N (BorderPrimitive attached to m_visual)
43
+ // ------Focus Visual Container (created when hosting focus visuals)
44
+ // |
45
+ // |------Inner Focus Visual
46
+ // |
47
+ // ------ Border Visuals x N (BorderPrimitive)
48
+ // |------Outer Focus Visual
49
+ // |
50
+ // ------ Border Visuals x N (BorderPrimitive)
51
+
35
52
  ComponentView::ComponentView(
36
53
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
37
54
  facebook::react::Tag tag,
38
55
  winrt::Microsoft::ReactNative::ReactContext const &reactContext,
39
- ComponentViewFeatures flags)
40
- : base_type(tag, reactContext), m_compContext(compContext), m_flags(flags) {
56
+ ComponentViewFeatures flags,
57
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder)
58
+ : base_type(tag, reactContext, builder), m_compContext(compContext), m_flags(flags) {
41
59
  m_outerVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
42
60
  // CreateContainerVisual in ICompositionContext
43
- m_focusVisual = compContext.CreateFocusVisual();
44
- m_outerVisual.InsertAt(m_focusVisual.InnerVisual(), 0);
45
61
  }
46
62
 
47
63
  ComponentView::~ComponentView() {
@@ -68,9 +84,21 @@ void ComponentView::onThemeChanged() noexcept {
68
84
  }
69
85
  }
70
86
 
71
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
72
- m_needsBorderUpdate = true;
73
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
87
+ if (m_borderPrimitive) {
88
+ m_borderPrimitive->onThemeChanged(
89
+ m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps()));
90
+ }
91
+ if (m_componentHostingFocusVisual) {
92
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
93
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
94
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->onThemeChanged(
95
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
96
+ }
97
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
98
+ auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
99
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->onThemeChanged(
100
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
101
+ }
74
102
  }
75
103
 
76
104
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
@@ -131,13 +159,25 @@ void ComponentView::updateProps(
131
159
  }
132
160
  }
133
161
 
134
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
135
- updateBorderProps(oldViewProps, newViewProps);
162
+ if (m_borderPrimitive) {
163
+ m_borderPrimitive->updateProps(oldViewProps, newViewProps);
164
+ }
165
+
166
+ if (m_componentHostingFocusVisual) {
167
+ if (!newViewProps.enableFocusRing) {
168
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
169
+ }
170
+
171
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
172
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
173
+ }
174
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
175
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
176
+ }
136
177
  }
137
178
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
138
179
  updateShadowProps(oldViewProps, newViewProps);
139
180
  }
140
-
141
181
  if (oldViewProps.tooltip != newViewProps.tooltip) {
142
182
  if (!m_tooltipTracked && newViewProps.tooltip) {
143
183
  TooltipService::GetCurrent(m_reactContext.Properties())->StartTracking(*this);
@@ -155,13 +195,84 @@ void ComponentView::updateLayoutMetrics(
155
195
  facebook::react::LayoutMetrics const &layoutMetrics,
156
196
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
157
197
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
158
- updateBorderLayoutMetrics(layoutMetrics, *viewProps());
198
+ updateClippingPath(layoutMetrics, *viewProps());
199
+ OuterVisual().Size(
200
+ {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
201
+ layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
202
+ OuterVisual().Offset({
203
+ layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
204
+ layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
205
+ 0.0f,
206
+ });
159
207
  }
160
208
 
161
209
  base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
210
+
211
+ if (layoutMetrics != oldLayoutMetrics) {
212
+ if (m_borderPrimitive) {
213
+ m_borderPrimitive->markNeedsUpdate();
214
+ }
215
+
216
+ if (m_componentHostingFocusVisual) {
217
+ m_componentHostingFocusVisual->updateFocusLayoutMetrics();
218
+ }
219
+ }
220
+
162
221
  UpdateCenterPropertySet();
163
222
  }
164
223
 
224
+ void ComponentView::updateFocusLayoutMetrics() noexcept {
225
+ facebook::react::RectangleEdges<bool> nudgeEdges;
226
+ auto scaleFactor = m_focusPrimitive->m_focusVisualComponent->m_layoutMetrics.pointScaleFactor;
227
+ if (m_focusPrimitive) {
228
+ if (m_focusPrimitive->m_focusOuterPrimitive) {
229
+ auto outerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(false /*inner*/);
230
+
231
+ if (outerFocusMetrics.frame.origin.x < 0) {
232
+ nudgeEdges.left = true;
233
+ }
234
+ if (outerFocusMetrics.frame.origin.y < 0) {
235
+ nudgeEdges.top = true;
236
+ }
237
+ if (outerFocusMetrics.frame.getMaxX() > m_layoutMetrics.frame.getMaxX()) {
238
+ nudgeEdges.right = true;
239
+ }
240
+ if (outerFocusMetrics.frame.getMaxY() > m_layoutMetrics.frame.getMaxY()) {
241
+ nudgeEdges.bottom = true;
242
+ }
243
+
244
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Size(
245
+ {outerFocusMetrics.frame.size.width * scaleFactor -
246
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
247
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0),
248
+ outerFocusMetrics.frame.size.height * scaleFactor -
249
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
250
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0)});
251
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Offset(
252
+ {nudgeEdges.left ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
253
+ nudgeEdges.top ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
254
+ 0.0f});
255
+ m_focusPrimitive->m_focusOuterPrimitive->markNeedsUpdate();
256
+ }
257
+
258
+ if (m_focusPrimitive->m_focusInnerPrimitive) {
259
+ auto innerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(true /*inner*/);
260
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Size(
261
+ {innerFocusMetrics.frame.size.width * scaleFactor -
262
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
263
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0),
264
+ innerFocusMetrics.frame.size.height * scaleFactor -
265
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
266
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0)});
267
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Offset(
268
+ {nudgeEdges.left ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
269
+ nudgeEdges.top ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
270
+ 0.0f});
271
+ m_focusPrimitive->m_focusInnerPrimitive->markNeedsUpdate();
272
+ }
273
+ }
274
+ }
275
+
165
276
  const facebook::react::LayoutMetrics &ComponentView::layoutMetrics() const noexcept {
166
277
  return m_layoutMetrics;
167
278
  }
@@ -199,7 +310,27 @@ void ComponentView::FinalizeTransform(
199
310
 
200
311
  void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept {
201
312
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
202
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
313
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps());
314
+ if (!m_borderPrimitive && BorderPrimitive::requiresBorder(borderMetrics, theme())) {
315
+ m_borderPrimitive = std::make_shared<BorderPrimitive>(*this, Visual());
316
+ }
317
+
318
+ if (m_borderPrimitive) {
319
+ m_borderPrimitive->finalize(m_layoutMetrics, borderMetrics);
320
+ }
321
+ }
322
+
323
+ if (m_componentHostingFocusVisual) {
324
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
325
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
326
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->finalize(
327
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
328
+ }
329
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
330
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
331
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->finalize(
332
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
333
+ }
203
334
  }
204
335
 
205
336
  if (m_FinalizeTransform) {
@@ -213,7 +344,12 @@ void ComponentView::onLostFocus(
213
344
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
214
345
  if (args.OriginalSource() == Tag()) {
215
346
  m_eventEmitter->onBlur();
216
- showFocusVisual(false);
347
+
348
+ if (m_componentHostingFocusVisual) {
349
+ auto s = get_strong();
350
+
351
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
352
+ }
217
353
  if (m_uiaProvider) {
218
354
  winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
219
355
  m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false);
@@ -222,12 +358,47 @@ void ComponentView::onLostFocus(
222
358
  base_type::onLostFocus(args);
223
359
  }
224
360
 
361
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::visualToHostFocus() noexcept {
362
+ return OuterVisual();
363
+ }
364
+
365
+ // We want to host focus visuals as close to the focused component as possible. However since the focus visuals extend
366
+ // past the bounds of the component, in cases where additional components are positioned directly next to this one, we'd
367
+ // get zorder issues causing most of the focus rect to be obscured. So we go up the tree until we find a component who's
368
+ // bounds will fix the entire focus rect.
369
+ winrt::com_ptr<ComponentView> ComponentView::focusVisualRoot(const facebook::react::Rect &focusRect) noexcept {
370
+ auto compVisual =
371
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(OuterVisual());
372
+ if (!compVisual) {
373
+ return get_strong();
374
+ // When not using lifted composition, force the focus visual to host within its own component, as we do not support
375
+ // ParentForTransform
376
+ }
377
+
378
+ if (facebook::react::Rect::intersect(focusRect, m_layoutMetrics.frame) == focusRect) {
379
+ return get_strong();
380
+ }
381
+
382
+ if (!m_parent) {
383
+ return get_strong();
384
+ }
385
+
386
+ return m_parent.as<ComponentView>()->focusVisualRoot(
387
+ {{focusRect.origin.x + m_layoutMetrics.frame.origin.x, focusRect.origin.y + m_layoutMetrics.frame.origin.y},
388
+ focusRect.size});
389
+ }
390
+
225
391
  void ComponentView::onGotFocus(
226
392
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
227
393
  if (args.OriginalSource() == Tag()) {
228
394
  m_eventEmitter->onFocus();
229
- if (m_enableFocusVisual) {
230
- showFocusVisual(true);
395
+ if (viewProps()->enableFocusRing) {
396
+ facebook::react::Rect focusRect = m_layoutMetrics.frame;
397
+ focusRect.origin.x -= (FOCUS_VISUAL_WIDTH * 2);
398
+ focusRect.origin.y -= (FOCUS_VISUAL_WIDTH * 2);
399
+ focusRect.size.width += (FOCUS_VISUAL_WIDTH * 2);
400
+ focusRect.size.height += (FOCUS_VISUAL_WIDTH * 2);
401
+ focusVisualRoot(focusRect)->hostFocusVisual(true, get_strong());
231
402
  }
232
403
  if (m_uiaProvider) {
233
404
  auto spProviderSimple = m_uiaProvider.try_as<IRawElementProviderSimple>();
@@ -325,6 +496,10 @@ void ComponentView::ReleasePointerCapture(
325
496
  ->ReleasePointerCapture(pointer, static_cast<facebook::react::Tag>(Tag()));
326
497
  }
327
498
 
499
+ void ComponentView::SetViewFeatures(ComponentViewFeatures viewFeatures) noexcept {
500
+ m_flags = viewFeatures;
501
+ }
502
+
328
503
  RECT ComponentView::getClientRect() const noexcept {
329
504
  RECT rc{0};
330
505
  facebook::react::Point parentOffset{0};
@@ -356,877 +531,112 @@ const facebook::react::SharedViewEventEmitter &ComponentView::GetEventEmitter()
356
531
  return m_eventEmitter;
357
532
  }
358
533
 
359
- std::array<
360
- winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual,
361
- ComponentView::SpecialBorderLayerCount>
362
- ComponentView::FindSpecialBorderLayers() const noexcept {
363
- std::array<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual, SpecialBorderLayerCount> layers{
364
- nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
365
-
366
- if (m_numBorderVisuals) {
367
- for (uint8_t i = 0; i < m_numBorderVisuals; i++) {
368
- auto visual = Visual().GetAt(i);
369
- layers[i] = visual.as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>();
370
- }
371
- }
372
-
373
- return layers;
374
- }
375
-
376
- struct RoundedPathParameters {
377
- float topLeftRadiusX = 0;
378
- float topLeftRadiusY = 0;
379
- float topRightRadiusX = 0;
380
- float topRightRadiusY = 0;
381
- float bottomRightRadiusX = 0;
382
- float bottomRightRadiusY = 0;
383
- float bottomLeftRadiusX = 0;
384
- float bottomLeftRadiusY = 0;
385
- };
386
-
387
- /*
388
- * Creates and returns a PathGeometry object used to clip the visuals of an element when a BorderRadius is set.
389
- * Can also be used as part of a GeometryGroup for drawing a rounded border / innerstroke when called from
390
- * GetGeometryForRoundedBorder. "params" defines the radii (horizontal and vertical) for each corner (top left, top
391
- * right, bottom right, bottom left). "rectPathGeometry" defines the bounding box of the generated shape.
392
- */
393
- static winrt::com_ptr<ID2D1PathGeometry> GenerateRoundedRectPathGeometry(
394
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
395
- const RoundedPathParameters &params,
396
- const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
397
- winrt::com_ptr<ID2D1PathGeometry> pathGeometry;
398
- winrt::com_ptr<ID2D1Factory1> spD2dFactory;
399
- compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
400
- spD2dFactory.put());
401
-
402
- // Create a path geometry.
403
- HRESULT hr = spD2dFactory->CreatePathGeometry(pathGeometry.put());
404
- if (FAILED(hr)) {
405
- assert(false);
406
- return nullptr;
407
- }
408
-
409
- // Write to the path geometry using the geometry sink.
410
- winrt::com_ptr<ID2D1GeometrySink> spSink = nullptr;
411
- hr = pathGeometry->Open(spSink.put());
412
-
413
- if (FAILED(hr)) {
414
- assert(false);
415
- return nullptr;
416
- }
417
-
418
- float left = rectPathGeometry.left;
419
- float right = rectPathGeometry.right;
420
- float top = rectPathGeometry.top;
421
- float bottom = rectPathGeometry.bottom;
422
-
423
- // This function uses Cubic Beziers to approximate Arc segments, even though D2D supports arcs.
424
- // This is INTENTIONAL. D2D Arc Segments are eventually converted into cubic beziers, but this
425
- // is done in such a way that we don't have control over how many bezier curve segments are used
426
- // for each arc. We need to ensure that we always use the same number of control points so that
427
- // our paths can be used in a PathKeyFrameAnimation.
428
- // Value for control point scale factor derived from methods described in:
429
- // https://web.archive.org/web/20200322075504/http://itc.ktu.lt/index.php/ITC/article/download/11812/6479
430
- constexpr float controlPointScaleFactor = 0.44771528244f; // 1 - (4 * (sqrtf(2.0f) - 1) / 3.0f);
431
-
432
- #ifdef DEBUG
433
- // std::sqrtf is not constexpr, so we precalculated this and wrote it in a constexpr form above.
434
- // On debug, we should still check that the values are equivalent, though.
435
- static float calculatedScaleFactor = 1 - (4 * (sqrtf(2.0f) - 1) / 3.0f);
436
- assert(controlPointScaleFactor == calculatedScaleFactor);
437
- #endif // DEBUG
438
-
439
- bool needsConsistentNumberOfControlPoints = true; // VisualVersion::IsUseWinCompClippingRegionEnabled();
440
-
441
- if (needsConsistentNumberOfControlPoints || (params.topLeftRadiusX != 0.0 && params.topLeftRadiusY != 0.0)) {
442
- spSink->BeginFigure(D2D1::Point2F(left + params.topLeftRadiusX, top), D2D1_FIGURE_BEGIN_FILLED);
443
- } else {
444
- spSink->BeginFigure(D2D1::Point2F(left, top), D2D1_FIGURE_BEGIN_FILLED);
445
- }
446
-
447
- // Move to the top right corner
448
- spSink->AddLine(D2D1::Point2F(right - params.topRightRadiusX, top));
449
- if (needsConsistentNumberOfControlPoints) {
450
- D2D1_BEZIER_SEGMENT arcSegmentTopRight = {
451
- D2D1::Point2F(right - controlPointScaleFactor * params.topRightRadiusX, top),
452
- D2D1::Point2F(right, top + controlPointScaleFactor * params.topRightRadiusY),
453
- D2D1::Point2F(right, top + params.topRightRadiusY)};
454
-
455
- spSink->AddBezier(&arcSegmentTopRight);
456
- } else if (params.topRightRadiusX != 0.0 && params.topRightRadiusY != 0.0) {
457
- D2D1_ARC_SEGMENT arcSegmentTopRight = {
458
- D2D1::Point2F(right, top + params.topRightRadiusY),
459
- D2D1::SizeF(params.topRightRadiusX, params.topRightRadiusY),
460
- 0.0f,
461
- D2D1_SWEEP_DIRECTION_CLOCKWISE,
462
- D2D1_ARC_SIZE_SMALL};
463
-
464
- spSink->AddArc(&arcSegmentTopRight);
465
- } else {
466
- spSink->AddLine(D2D1::Point2F(right, top));
467
- }
468
-
469
- // Move to the bottom right corner
470
- spSink->AddLine(D2D1::Point2F(right, bottom - params.bottomRightRadiusY));
471
- if (needsConsistentNumberOfControlPoints) {
472
- D2D1_BEZIER_SEGMENT arcSegmentBottomRight = {
473
- D2D1::Point2F(right, bottom - controlPointScaleFactor * params.bottomRightRadiusY),
474
- D2D1::Point2F(right - controlPointScaleFactor * params.bottomRightRadiusX, bottom),
475
- D2D1::Point2F(right - params.bottomRightRadiusX, bottom)};
476
-
477
- spSink->AddBezier(&arcSegmentBottomRight);
478
- } else if (params.bottomRightRadiusX != 0.0 && params.bottomRightRadiusY != 0.0) {
479
- D2D1_ARC_SEGMENT arcSegmentBottomRight = {
480
- D2D1::Point2F(right - params.bottomRightRadiusX, bottom),
481
- D2D1::SizeF(params.bottomRightRadiusX, params.bottomRightRadiusY),
482
- 0.0f,
483
- D2D1_SWEEP_DIRECTION_CLOCKWISE,
484
- D2D1_ARC_SIZE_SMALL};
485
-
486
- spSink->AddArc(&arcSegmentBottomRight);
487
- } else {
488
- spSink->AddLine(D2D1::Point2F(right, bottom));
489
- }
490
-
491
- // Move to the bottom left corner
492
- spSink->AddLine(D2D1::Point2F(left + params.bottomLeftRadiusX, bottom));
493
- if (needsConsistentNumberOfControlPoints) {
494
- D2D1_BEZIER_SEGMENT arcSegmentBottomLeft = {
495
- D2D1::Point2F(left + controlPointScaleFactor * params.bottomLeftRadiusX, bottom),
496
- D2D1::Point2F(left, bottom - controlPointScaleFactor * params.bottomLeftRadiusY),
497
- D2D1::Point2F(left, bottom - params.bottomLeftRadiusY)};
498
-
499
- spSink->AddBezier(&arcSegmentBottomLeft);
500
- } else if (params.bottomLeftRadiusX != 0.0 && params.bottomLeftRadiusY != 0.0) {
501
- D2D1_ARC_SEGMENT arcSegmentBottomLeft = {
502
- D2D1::Point2F(left, bottom - params.bottomLeftRadiusY),
503
- D2D1::SizeF(params.bottomLeftRadiusX, params.bottomLeftRadiusY),
504
- 0.0f,
505
- D2D1_SWEEP_DIRECTION_CLOCKWISE,
506
- D2D1_ARC_SIZE_SMALL};
507
-
508
- spSink->AddArc(&arcSegmentBottomLeft);
509
- } else {
510
- spSink->AddLine(D2D1::Point2F(left, bottom));
511
- }
512
-
513
- // Move to the top left corner
514
- spSink->AddLine(D2D1::Point2F(left, top + params.topLeftRadiusY));
515
- if (needsConsistentNumberOfControlPoints) {
516
- D2D1_BEZIER_SEGMENT arcSegmentTopLeft = {
517
- D2D1::Point2F(left, top + controlPointScaleFactor * params.topLeftRadiusY),
518
- D2D1::Point2F(left + controlPointScaleFactor * params.topLeftRadiusX, top),
519
- D2D1::Point2F(left + params.topLeftRadiusX, top)};
520
-
521
- spSink->AddBezier(&arcSegmentTopLeft);
522
- } else if (params.topLeftRadiusX != 0.0 && params.topLeftRadiusY != 0.0) {
523
- D2D1_ARC_SEGMENT arcSegmentTopLeft = {
524
- D2D1::Point2F(left + params.topLeftRadiusX, top),
525
- D2D1::SizeF(params.topLeftRadiusX, params.topLeftRadiusY),
526
- 0.0f,
527
- D2D1_SWEEP_DIRECTION_CLOCKWISE,
528
- D2D1_ARC_SIZE_SMALL};
529
-
530
- spSink->AddArc(&arcSegmentTopLeft);
531
- } else {
532
- spSink->AddLine(D2D1::Point2F(left, top));
533
- }
534
-
535
- spSink->EndFigure(D2D1_FIGURE_END_CLOSED);
536
- spSink->Close();
537
-
538
- return pathGeometry;
539
- }
540
-
541
- RoundedPathParameters GenerateRoundedPathParameters(
542
- const facebook::react::RectangleCorners<float> &baseRadius,
543
- const facebook::react::RectangleEdges<float> &inset,
544
- const facebook::react::Size &pathSize) noexcept {
545
- RoundedPathParameters result;
546
-
547
- if (pathSize.width == 0 || pathSize.height == 0) {
548
- return result;
549
- }
550
-
551
- float totalTopRadius = baseRadius.topLeft + baseRadius.topRight;
552
- float totalRightRadius = baseRadius.topRight + baseRadius.bottomRight;
553
- float totalBottomRadius = baseRadius.bottomRight + baseRadius.bottomLeft;
554
- float totalLeftRadius = baseRadius.bottomLeft + baseRadius.topLeft;
555
-
556
- float maxHorizontalRadius = std::max(totalTopRadius, totalBottomRadius);
557
- float maxVerticalRadius = std::max(totalLeftRadius, totalRightRadius);
558
-
559
- double totalWidth = inset.left + inset.right + pathSize.width;
560
- double totalHeight = inset.top + inset.bottom + pathSize.height;
561
-
562
- float scaleHoriz = static_cast<float>(maxHorizontalRadius / totalWidth);
563
- float scaleVert = static_cast<float>(maxVerticalRadius / totalHeight);
564
-
565
- float maxScale = std::max(1.0f, std::max(scaleHoriz, scaleVert));
566
-
567
- result.topLeftRadiusX = std::max(0.0f, baseRadius.topLeft / maxScale - inset.left);
568
- result.topLeftRadiusY = std::max(0.0f, baseRadius.topLeft / maxScale - inset.top);
569
- result.topRightRadiusX = std::max(0.0f, baseRadius.topRight / maxScale - inset.right);
570
- result.topRightRadiusY = std::max(0.0f, baseRadius.topRight / maxScale - inset.top);
571
- result.bottomRightRadiusX = std::max(0.0f, baseRadius.bottomRight / maxScale - inset.right);
572
- result.bottomRightRadiusY = std::max(0.0f, baseRadius.bottomRight / maxScale - inset.bottom);
573
- result.bottomLeftRadiusX = std::max(0.0f, baseRadius.bottomLeft / maxScale - inset.left);
574
- result.bottomLeftRadiusY = std::max(0.0f, baseRadius.bottomLeft / maxScale - inset.bottom);
575
-
576
- return result;
577
- }
578
-
579
- static winrt::com_ptr<ID2D1PathGeometry> GenerateRoundedRectPathGeometry(
580
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
581
- const facebook::react::RectangleCorners<float> &baseRadius,
582
- const facebook::react::RectangleEdges<float> &inset,
583
- const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
584
- RoundedPathParameters params = GenerateRoundedPathParameters(
585
- baseRadius,
586
- inset,
587
- {rectPathGeometry.right - rectPathGeometry.left, rectPathGeometry.bottom - rectPathGeometry.top});
588
-
589
- return GenerateRoundedRectPathGeometry(compContext, params, rectPathGeometry);
590
- }
591
-
592
- void DrawShape(
593
- ID2D1RenderTarget *pRT,
594
- const D2D1_RECT_F &rect,
595
- ID2D1Brush *brush,
596
- FLOAT strokeWidth,
597
- ID2D1StrokeStyle *strokeStyle) {
598
- pRT->DrawRectangle(rect, brush, strokeWidth, strokeStyle);
599
- }
600
-
601
- void DrawShape(ID2D1RenderTarget *pRT, ID2D1GeometryGroup &geometry, ID2D1Brush *brush, FLOAT, ID2D1StrokeStyle *) {
602
- pRT->FillGeometry(&geometry, brush);
603
- }
604
-
605
- void DrawShape(
606
- ID2D1RenderTarget *pRT,
607
- ID2D1PathGeometry &geometry,
608
- ID2D1Brush *brush,
609
- FLOAT strokeWidth,
610
- ID2D1StrokeStyle *strokeStyle) {
611
- pRT->DrawGeometry(&geometry, brush, strokeWidth, strokeStyle);
612
- }
613
-
614
- template <typename TShape>
615
- void SetBorderLayerPropertiesCommon(
616
- winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
617
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
618
- winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual &layer,
619
- TShape &shape,
620
- winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
621
- &borderTexture,
622
- const D2D1_RECT_F &textureRect,
623
- facebook::react::Point anchorPoint,
624
- facebook::react::Point anchorOffset,
625
- winrt::Windows::Foundation::Numerics::float2 size,
626
- winrt::Windows::Foundation::Numerics::float2 relativeSizeAdjustment,
627
- FLOAT strokeWidth,
628
- const facebook::react::SharedColor &borderColor,
629
- facebook::react::BorderStyle borderStyle) {
630
- layer.Offset({anchorOffset.x, anchorOffset.y, 0}, {anchorPoint.x, anchorPoint.y, 0});
631
- layer.RelativeSizeWithOffset(size, relativeSizeAdjustment);
632
- layer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
633
-
634
- if ((textureRect.right - textureRect.left) <= 0 || (textureRect.bottom - textureRect.top) <= 0)
635
- return;
636
-
637
- auto surface = compContext.CreateDrawingSurfaceBrush(
638
- {(textureRect.right - textureRect.left), (textureRect.bottom - textureRect.top)},
639
- winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
640
- winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);
641
- surface.as(borderTexture);
642
-
643
- layer.Brush(surface);
644
-
645
- POINT offset;
646
- ::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(
647
- surface, 1.0f /* We have already done the dpi scaling */, &offset);
648
- if (auto pRT = autoDraw.GetRenderTarget()) {
649
- // Clear with transparency
650
- pRT->Clear();
651
-
652
- if (!facebook::react::isColorMeaningful(borderColor)) {
653
- return;
654
- }
655
-
656
- winrt::com_ptr<ID2D1Factory> spFactory;
657
- pRT->GetFactory(spFactory.put());
658
- assert(spFactory);
659
- if (spFactory == nullptr)
660
- return;
661
-
662
- winrt::com_ptr<ID2D1SolidColorBrush> spBorderBrush;
663
- pRT->CreateSolidColorBrush(theme->D2DColor(*borderColor), spBorderBrush.put());
664
- assert(spBorderBrush);
665
- if (spBorderBrush == nullptr)
666
- return;
667
-
668
- winrt::com_ptr<ID2D1StrokeStyle> spStrokeStyle;
669
-
670
- enum class BorderStyle { Solid, Dotted, Dashed };
671
-
672
- if (borderStyle == facebook::react::BorderStyle::Dotted || borderStyle == facebook::react::BorderStyle::Dashed) {
673
- const auto capStyle =
674
- borderStyle == facebook::react::BorderStyle::Dashed ? D2D1_CAP_STYLE_FLAT : D2D1_CAP_STYLE_ROUND;
675
- const auto strokeStyleProps = D2D1::StrokeStyleProperties(
676
- capStyle,
677
- capStyle,
678
- capStyle,
679
- D2D1_LINE_JOIN_MITER,
680
- 10.0f,
681
- borderStyle == facebook::react::BorderStyle::Dashed ? D2D1_DASH_STYLE_DASH : D2D1_DASH_STYLE_DOT,
682
- 0.0f);
683
- spFactory->CreateStrokeStyle(&strokeStyleProps, nullptr, 0, spStrokeStyle.put());
684
- }
685
- D2D1::Matrix3x2F originalTransform;
686
- D2D1::Matrix3x2F translationTransform =
687
- D2D1::Matrix3x2F::Translation(-textureRect.left + offset.x, -textureRect.top + offset.y);
688
-
689
- pRT->GetTransform(&originalTransform);
690
- translationTransform = originalTransform * translationTransform;
691
-
692
- pRT->SetTransform(translationTransform);
693
-
694
- DrawShape(pRT, shape, spBorderBrush.get(), strokeWidth, spStrokeStyle.get());
695
-
696
- pRT->SetTransform(originalTransform);
697
- }
698
- }
699
-
700
- template <typename TShape>
701
- void SetBorderLayerProperties(
702
- winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
703
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
704
- winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual &layer,
705
- TShape &shape,
706
- winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
707
- &borderTexture,
708
- const D2D1_RECT_F &textureRect,
709
- facebook::react::Point anchorPoint,
710
- facebook::react::Point anchorOffset,
711
- winrt::Windows::Foundation::Numerics::float2 size,
712
- winrt::Windows::Foundation::Numerics::float2 relativeSizeAdjustment,
713
- FLOAT strokeWidth,
714
- const facebook::react::SharedColor &borderColor,
715
- facebook::react::BorderStyle borderStyle) {
716
- if constexpr (!std::is_base_of_v<ID2D1GeometryGroup, TShape>) {
717
- SetBorderLayerPropertiesCommon(
718
- theme,
719
- compContext,
720
- layer,
721
- shape,
722
- borderTexture,
723
- textureRect,
724
- anchorPoint,
725
- anchorOffset,
726
- size,
727
- relativeSizeAdjustment,
728
- strokeWidth,
729
- borderColor,
730
- borderStyle);
731
- } else {
732
- // if (VisualVersion::IsUseWinCompClippingRegionEnabled())
733
- {
734
- layer.Offset({anchorOffset.x, anchorOffset.y, 0}, {anchorPoint.x, anchorPoint.y, 0});
735
- layer.RelativeSizeWithOffset(
736
- {textureRect.right - textureRect.left, textureRect.bottom - textureRect.top}, {0.0f, 0.0f});
737
-
738
- layer.Brush(theme->Brush(*borderColor));
739
-
740
- winrt::com_ptr<ID2D1Factory1> spD2dFactory;
741
- compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
742
- spD2dFactory.put());
743
-
744
- winrt::com_ptr<ID2D1TransformedGeometry> transformedShape;
745
- D2D1::Matrix3x2F translationTransform = D2D1::Matrix3x2F::Translation(-textureRect.left, -textureRect.top);
746
- winrt::check_hresult(
747
- spD2dFactory->CreateTransformedGeometry(&shape, &translationTransform, transformedShape.put()));
748
-
749
- layer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
750
- transformedShape.get());
751
- }
752
- /*
753
- else
754
- {
755
- SetBorderLayerPropertiesCommon(theme, comContext, layer, shape, borderTexture, textureRect,
756
- anchorPoint, anchorOffset, strokeWidth, borderColor, borderStyle);
757
- }
758
- */
759
- }
760
- }
761
-
762
- namespace AnchorPosition {
763
- const float Left = 0.0;
764
- const float Center = 0.5;
765
- const float Right = 1.0;
766
- const float Top = 0.0;
767
- const float Bottom = 1.0;
768
- } // namespace AnchorPosition
769
-
770
- template <typename TShape>
771
- void DrawAllBorderLayers(
772
- winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
773
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
774
- std::array<
775
- winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual,
776
- ComponentView::SpecialBorderLayerCount> &spBorderLayers,
777
- TShape &shape,
778
- const facebook::react::BorderWidths &borderWidths,
779
- const facebook::react::BorderRadii &borderRadii,
780
- float textureWidth,
781
- float textureHeight,
782
- const facebook::react::BorderColors &borderColors,
783
- facebook::react::BorderStyle borderStyle) {
784
- // Now that we've drawn our nice border in one layer, split it into its component layers
785
- winrt::com_ptr<::Microsoft::ReactNative::Composition::Experimental::ICompositionDrawingSurfaceInterop>
786
- spTextures[ComponentView::SpecialBorderLayerCount];
787
-
788
- // Set component border properties
789
- // Top Left Corner
790
- SetBorderLayerProperties(
791
- theme,
792
- compContext,
793
- spBorderLayers[0],
794
- shape,
795
- spTextures[0], // Target Layer, Source Texture, Target Texture
796
- {0,
797
- 0,
798
- borderRadii.topLeft + borderWidths.left,
799
- borderRadii.topLeft + borderWidths.top}, // Texture Left, Top, Width, Height
800
- {AnchorPosition::Left, AnchorPosition::Top}, // Layer Anchor Point
801
- {0, 0}, // Layer Anchor Offset
802
- {borderRadii.topLeft + borderWidths.left, borderRadii.topLeft + borderWidths.top}, // size
803
- {0.0f, 0.0f}, // relativeSize
804
- std::max(borderWidths.left, borderWidths.top),
805
- borderColors.left ? borderColors.left : borderColors.top,
806
- borderStyle);
807
-
808
- // Top Edge Border
809
- SetBorderLayerProperties(
810
- theme,
811
- compContext,
812
- spBorderLayers[1],
813
- shape,
814
- spTextures[1],
815
- {borderRadii.topLeft + borderWidths.left,
816
- 0,
817
- textureWidth - (borderRadii.topRight + borderWidths.right),
818
- borderWidths.top},
819
- {AnchorPosition::Left, AnchorPosition::Top},
820
- {borderRadii.topLeft + borderWidths.left, 0},
821
- {-(borderRadii.topLeft + borderWidths.left + borderRadii.topRight + borderWidths.right),
822
- borderWidths.top}, // size
823
- {1.0f, 0.0f}, // relativeSize
824
- borderWidths.top,
825
- borderColors.top,
826
- borderStyle);
827
-
828
- // Top Right Corner Border
829
- SetBorderLayerProperties(
830
- theme,
831
- compContext,
832
- spBorderLayers[2],
833
- shape,
834
- spTextures[2],
835
- {textureWidth - (borderRadii.topRight + borderWidths.right),
836
- 0,
837
- textureWidth,
838
- borderRadii.topRight + borderWidths.top},
839
- {AnchorPosition::Right, AnchorPosition::Top},
840
- {-(borderRadii.topRight + borderWidths.right), 0},
841
- {borderRadii.topRight + borderWidths.right, borderRadii.topRight + borderWidths.top},
842
- {0.0f, 0.0f},
843
- std::max(borderWidths.right, borderWidths.top),
844
- borderColors.right ? borderColors.right : borderColors.top,
845
- borderStyle);
846
-
847
- // Right Edge Border
848
- SetBorderLayerProperties(
849
- theme,
850
- compContext,
851
- spBorderLayers[3],
852
- shape,
853
- spTextures[3],
854
- {textureWidth - borderWidths.right,
855
- borderWidths.top + borderRadii.topRight,
856
- textureWidth,
857
- textureHeight - (borderWidths.bottom + borderRadii.bottomRight)},
858
- {AnchorPosition::Right, AnchorPosition::Top},
859
- {-borderWidths.right, borderWidths.top + borderRadii.topRight},
860
- {borderWidths.right,
861
- -(borderWidths.top + borderRadii.topRight + borderWidths.bottom + borderRadii.bottomRight)}, // size
862
- {0.0f, 1.0f},
863
- borderWidths.right,
864
- borderColors.right,
865
- borderStyle);
866
-
867
- // Bottom Right Corner Border
868
- SetBorderLayerProperties(
869
- theme,
870
- compContext,
871
- spBorderLayers[4],
872
- shape,
873
- spTextures[4],
874
- {textureWidth - (borderWidths.right + borderRadii.bottomRight),
875
- textureHeight - (borderWidths.bottom + borderRadii.bottomRight),
876
- textureWidth,
877
- textureHeight},
878
- {AnchorPosition::Right, AnchorPosition::Bottom},
879
- {-(borderWidths.right + borderRadii.bottomRight), -(borderWidths.bottom + borderRadii.bottomRight)},
880
- {borderWidths.right + borderRadii.bottomRight, borderWidths.bottom + borderRadii.bottomRight},
881
- {0, 0},
882
- std::max(borderWidths.right, borderWidths.bottom),
883
- borderColors.right ? borderColors.right : borderColors.bottom,
884
- borderStyle);
885
-
886
- // Bottom Edge Border
887
- SetBorderLayerProperties(
888
- theme,
889
- compContext,
890
- spBorderLayers[5],
891
- shape,
892
- spTextures[5],
893
- {borderWidths.left + borderRadii.bottomLeft,
894
- textureHeight - borderWidths.bottom,
895
- textureWidth - (borderWidths.right + borderRadii.bottomRight),
896
- textureHeight},
897
- {AnchorPosition::Left, AnchorPosition::Bottom},
898
- {borderWidths.left + borderRadii.bottomLeft, -borderWidths.bottom},
899
- {-(borderWidths.right + borderRadii.bottomLeft + borderWidths.left + borderRadii.bottomRight),
900
- borderWidths.bottom},
901
- {1.0f, 0.0f},
902
- borderWidths.bottom,
903
- borderColors.bottom,
904
- borderStyle);
905
-
906
- // Bottom Left Corner Border
907
- SetBorderLayerProperties(
908
- theme,
909
- compContext,
910
- spBorderLayers[6],
911
- shape,
912
- spTextures[6],
913
- {0,
914
- textureHeight - (borderWidths.bottom + borderRadii.bottomLeft),
915
- borderWidths.left + borderRadii.bottomLeft,
916
- textureHeight},
917
- {AnchorPosition::Left, AnchorPosition::Bottom},
918
- {0, -(borderWidths.bottom + borderRadii.bottomLeft)},
919
- {borderWidths.left + borderRadii.bottomLeft, borderWidths.bottom + borderRadii.bottomLeft},
920
- {0, 0},
921
- std::max(borderWidths.left, borderWidths.bottom),
922
- borderColors.left ? borderColors.left : borderColors.bottom,
923
- borderStyle);
924
-
925
- // Left Edge Border
926
- SetBorderLayerProperties(
927
- theme,
928
- compContext,
929
- spBorderLayers[7],
930
- shape,
931
- spTextures[7],
932
- {0,
933
- borderWidths.top + borderRadii.topLeft,
934
- borderWidths.left,
935
- textureHeight - (borderWidths.bottom + borderRadii.bottomLeft)},
936
- {AnchorPosition::Left, AnchorPosition::Top},
937
- {0, borderWidths.top + borderRadii.topLeft},
938
- {borderWidths.left, -(borderWidths.top + borderRadii.topLeft + borderWidths.bottom + borderRadii.bottomLeft)},
939
- {0, 1},
940
- borderWidths.left,
941
- borderColors.left,
942
- borderStyle);
943
- }
944
-
945
- winrt::com_ptr<ID2D1GeometryGroup> GetGeometryForRoundedBorder(
946
- winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
947
- const facebook::react::RectangleCorners<float> &radius,
948
- const facebook::react::RectangleEdges<float> &inset,
949
- const facebook::react::RectangleEdges<float> &thickness,
950
- const facebook::react::RectangleEdges<float> &rectPathGeometry) noexcept {
951
- winrt::com_ptr<ID2D1PathGeometry> outerPathGeometry =
952
- GenerateRoundedRectPathGeometry(compContext, radius, inset, rectPathGeometry);
953
-
954
- if (outerPathGeometry == nullptr) {
955
- assert(false);
956
- return nullptr;
957
- }
958
-
959
- facebook::react::RectangleEdges<float> rectInnerPathGeometry = {
960
- rectPathGeometry.left + thickness.left,
961
- rectPathGeometry.top + thickness.top,
962
- rectPathGeometry.right - thickness.right,
963
- rectPathGeometry.bottom - thickness.bottom};
964
-
965
- // Total thickness is larger than original element size.
966
- // Clamp inner rect to have a width/height of 0, but placed such that the ratio of side-thicknesses is respected.
967
- // We need to respect this ratio so that any animations work properly.
968
-
969
- if (rectInnerPathGeometry.left > rectInnerPathGeometry.right) {
970
- float leftRatio = thickness.left / (thickness.left + thickness.right);
971
- auto x = std::floor(rectPathGeometry.left + ((rectPathGeometry.right - rectPathGeometry.left) * leftRatio));
972
- rectInnerPathGeometry.left = x;
973
- rectInnerPathGeometry.right = x;
974
- }
975
-
976
- if (rectInnerPathGeometry.top > rectInnerPathGeometry.bottom) {
977
- float topRatio = thickness.top / (thickness.top + thickness.bottom);
978
- auto y = rectPathGeometry.top + std::floor((rectPathGeometry.top - rectPathGeometry.bottom) * topRatio);
979
- rectInnerPathGeometry.top = y;
980
- rectInnerPathGeometry.bottom = y;
981
- }
982
-
983
- facebook::react::RectangleEdges<float> innerInset = {
984
- inset.left + thickness.left,
985
- inset.top + thickness.top,
986
- inset.right + thickness.right,
987
- inset.bottom + thickness.bottom};
988
-
989
- winrt::com_ptr<ID2D1PathGeometry> innerPathGeometry =
990
- GenerateRoundedRectPathGeometry(compContext, radius, innerInset, rectInnerPathGeometry);
991
-
992
- if (innerPathGeometry == nullptr) {
993
- assert(false); // Failed to create inner pathGeometry for rounded border
994
- return nullptr;
995
- }
996
-
997
- ID2D1Geometry *ppGeometries[] = {outerPathGeometry.get(), innerPathGeometry.get()};
998
- winrt::com_ptr<ID2D1Factory1> spD2dFactory;
999
- compContext.as<::Microsoft::ReactNative::Composition::Experimental::ICompositionContextInterop>()->D2DFactory(
1000
- spD2dFactory.put());
1001
-
1002
- winrt::com_ptr<ID2D1GeometryGroup> geometryGroup = nullptr;
1003
- // Create a geometry group.
1004
- HRESULT hr = spD2dFactory->CreateGeometryGroup(
1005
- D2D1_FILL_MODE_ALTERNATE, ppGeometries, ARRAYSIZE(ppGeometries), geometryGroup.put());
1006
-
1007
- if (SUCCEEDED(hr)) {
1008
- return geometryGroup;
1009
- }
1010
- return nullptr;
1011
- }
1012
-
1013
- // We don't want half pixel borders, or border radii - they lead to blurry borders
1014
- // Also apply scale factor to the radii at this point
1015
- void pixelRoundBorderRadii(facebook::react::BorderRadii &borderRadii, float scaleFactor) noexcept {
1016
- // Always round radii down to avoid spikey circles
1017
- borderRadii.topLeft = std::floor(borderRadii.topLeft * scaleFactor);
1018
- borderRadii.topRight = std::floor(borderRadii.topRight * scaleFactor);
1019
- borderRadii.bottomLeft = std::floor(borderRadii.bottomLeft * scaleFactor);
1020
- borderRadii.bottomRight = std::floor(borderRadii.bottomRight * scaleFactor);
1021
- }
1022
-
1023
- void scaleAndPixelRoundBorderWidths(
1024
- facebook::react::LayoutMetrics const &layoutMetrics,
1025
- facebook::react::BorderMetrics &borderMetrics,
1026
- float scaleFactor) noexcept {
1027
- borderMetrics.borderWidths.left = (borderMetrics.borderWidths.left == 0)
1028
- ? 0.f
1029
- : std::max(1.f, std::round(borderMetrics.borderWidths.left * scaleFactor));
1030
- borderMetrics.borderWidths.top = (borderMetrics.borderWidths.top == 0)
1031
- ? 0.f
1032
- : std::max(1.f, std::round(borderMetrics.borderWidths.top * scaleFactor));
1033
- borderMetrics.borderWidths.right = (borderMetrics.borderWidths.right == 0)
1034
- ? 0.f
1035
- : std::max(1.f, std::round(borderMetrics.borderWidths.right * scaleFactor));
1036
- borderMetrics.borderWidths.bottom = (borderMetrics.borderWidths.bottom == 0)
1037
- ? 0.f
1038
- : std::max(1.f, std::round(borderMetrics.borderWidths.bottom * scaleFactor));
1039
-
1040
- // If we rounded both sides of the borderWidths up, we may have made the borderWidths larger than the total
1041
- if (layoutMetrics.frame.size.width * scaleFactor <
1042
- (borderMetrics.borderWidths.left + borderMetrics.borderWidths.right)) {
1043
- borderMetrics.borderWidths.right--;
1044
- }
1045
- if (layoutMetrics.frame.size.height * scaleFactor <
1046
- (borderMetrics.borderWidths.top + borderMetrics.borderWidths.bottom)) {
1047
- borderMetrics.borderWidths.bottom--;
1048
- }
1049
- }
1050
-
1051
- // react-native uses black as a default color when none is specified.
1052
- void assignDefaultBlackBorders(facebook::react::BorderMetrics &borderMetrics) noexcept {
1053
- if (!borderMetrics.borderColors.left) {
1054
- borderMetrics.borderColors.left = facebook::react::blackColor();
1055
- }
1056
- if (!borderMetrics.borderColors.top) {
1057
- borderMetrics.borderColors.top = facebook::react::blackColor();
1058
- }
1059
- if (!borderMetrics.borderColors.right) {
1060
- borderMetrics.borderColors.right = facebook::react::blackColor();
1061
- }
1062
- if (!borderMetrics.borderColors.bottom) {
1063
- borderMetrics.borderColors.bottom = facebook::react::blackColor();
1064
- }
1065
- }
1066
-
1067
- facebook::react::BorderMetrics resolveAndAlignBorderMetrics(
1068
- facebook::react::LayoutMetrics const &layoutMetrics,
1069
- const facebook::react::ViewProps &viewProps) noexcept {
1070
- auto borderMetrics = viewProps.resolveBorderMetrics(layoutMetrics);
1071
-
1072
- pixelRoundBorderRadii(borderMetrics.borderRadii, layoutMetrics.pointScaleFactor);
1073
- scaleAndPixelRoundBorderWidths(layoutMetrics, borderMetrics, layoutMetrics.pointScaleFactor);
1074
- assignDefaultBlackBorders(borderMetrics);
1075
- return borderMetrics;
534
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::OuterVisual() const noexcept {
535
+ return m_outerVisual ? m_outerVisual : Visual();
1076
536
  }
1077
537
 
1078
- bool ComponentView::TryUpdateSpecialBorderLayers(
1079
- winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
1080
- std::array<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual, SpecialBorderLayerCount>
1081
- &spBorderVisuals,
1082
- facebook::react::LayoutMetrics const &layoutMetrics,
1083
- const facebook::react::ViewProps &viewProps) noexcept {
1084
- auto borderMetrics = resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1085
- // We only handle a single borderStyle for now
1086
- auto borderStyle = borderMetrics.borderStyles.left;
1087
-
1088
- bool hasMeaningfulColor =
1089
- !borderMetrics.borderColors.isUniform() || !facebook::react::isColorMeaningful(borderMetrics.borderColors.left);
1090
- bool hasMeaningfulWidth = !borderMetrics.borderWidths.isUniform() || (borderMetrics.borderWidths.left != 0);
1091
- if (!hasMeaningfulColor && !hasMeaningfulWidth) {
1092
- return false;
1093
- }
1094
-
1095
- // Create the special border layers if they don't exist yet
1096
- if (!spBorderVisuals[0]) {
1097
- for (uint8_t i = 0; i < SpecialBorderLayerCount; i++) {
1098
- auto visual = m_compContext.CreateSpriteVisual();
1099
- Visual().InsertAt(visual, i);
1100
- spBorderVisuals[i] = std::move(visual);
1101
- m_numBorderVisuals++;
1102
- }
1103
- }
1104
-
1105
- float extentWidth = layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor;
1106
- float extentHeight = layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor;
1107
-
1108
- if (borderMetrics.borderRadii.topLeft != 0 || borderMetrics.borderRadii.topRight != 0 ||
1109
- borderMetrics.borderRadii.bottomLeft != 0 || borderMetrics.borderRadii.bottomRight != 0) {
1110
- if (borderStyle == facebook::react::BorderStyle::Dotted || borderStyle == facebook::react::BorderStyle::Dashed) {
1111
- // Because in DirectX geometry starts at the center of the stroke, we need to deflate
1112
- // rectangle by half the stroke width to render correctly.
1113
- facebook::react::RectangleEdges<float> rectPathGeometry = {
1114
- borderMetrics.borderWidths.left / 2.0f,
1115
- borderMetrics.borderWidths.top / 2.0f,
1116
- extentWidth - borderMetrics.borderWidths.right / 2.0f,
1117
- extentHeight - borderMetrics.borderWidths.bottom / 2.0f};
1118
-
1119
- winrt::com_ptr<ID2D1PathGeometry> pathGeometry =
1120
- GenerateRoundedRectPathGeometry(m_compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, rectPathGeometry);
1121
-
1122
- if (pathGeometry) {
1123
- DrawAllBorderLayers(
1124
- theme,
1125
- m_compContext,
1126
- spBorderVisuals,
1127
- *pathGeometry,
1128
- borderMetrics.borderWidths,
1129
- borderMetrics.borderRadii,
1130
- extentWidth,
1131
- extentHeight,
1132
- borderMetrics.borderColors,
1133
- borderStyle);
1134
- } else {
1135
- assert(false);
538
+ facebook::react::LayoutMetrics ComponentView::focusLayoutMetrics(bool inner) const noexcept {
539
+ facebook::react::LayoutMetrics layoutMetrics = m_layoutMetrics;
540
+ layoutMetrics.frame.origin.x -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
541
+ layoutMetrics.frame.origin.y -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
542
+ layoutMetrics.frame.size.height += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
543
+ layoutMetrics.frame.size.width += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
544
+ return layoutMetrics;
545
+ }
546
+
547
+ facebook::react::BorderMetrics ComponentView::focusBorderMetrics(
548
+ bool inner,
549
+ const facebook::react::LayoutMetrics &layoutMetrics) const noexcept {
550
+ facebook::react::BorderMetrics metrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, *viewProps());
551
+ facebook::react::Color innerColor;
552
+ innerColor.m_color = {1, 0, 0, 0};
553
+ innerColor.m_platformColor.push_back(inner ? "FocusVisualSecondary" : "FocusVisualPrimary");
554
+ metrics.borderColors.bottom = metrics.borderColors.left = metrics.borderColors.right = metrics.borderColors.top =
555
+ innerColor;
556
+ if (metrics.borderRadii.bottomLeft != 0)
557
+ metrics.borderRadii.bottomLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
558
+ if (metrics.borderRadii.bottomRight != 0)
559
+ metrics.borderRadii.bottomRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
560
+ if (metrics.borderRadii.topLeft != 0)
561
+ metrics.borderRadii.topLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
562
+ if (metrics.borderRadii.topRight != 0)
563
+ metrics.borderRadii.topRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
564
+
565
+ metrics.borderStyles.bottom = metrics.borderStyles.left = metrics.borderStyles.right = metrics.borderStyles.top =
566
+ facebook::react::BorderStyle::Solid;
567
+ metrics.borderWidths.bottom = metrics.borderWidths.left = metrics.borderWidths.right = metrics.borderWidths.top =
568
+ FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor;
569
+ return metrics;
570
+ }
571
+
572
+ void ComponentView::hostFocusVisual(bool show, winrt::com_ptr<ComponentView> view) noexcept {
573
+ if ((view->m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
574
+ // Any previous view showing focus visuals should have removed them before another shows it
575
+ assert(
576
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
577
+ m_focusPrimitive->m_focusVisualComponent == view);
578
+ assert(
579
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
580
+ view->m_componentHostingFocusVisual.get() == this);
581
+ if (show && !view->m_componentHostingFocusVisual) {
582
+ view->m_componentHostingFocusVisual = get_strong();
583
+
584
+ if (!m_focusPrimitive) {
585
+ m_focusPrimitive = std::make_unique<FocusPrimitive>();
586
+ }
587
+ m_focusPrimitive->m_focusVisualComponent = view;
588
+
589
+ if (!m_focusPrimitive->m_focusVisual) {
590
+ m_focusPrimitive->m_focusVisual = m_compContext.CreateSpriteVisual();
591
+ auto hostingVisual =
592
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
593
+ visualToHostFocus())
594
+ .as<winrt::Microsoft::UI::Composition::ContainerVisual>();
595
+ if (hostingVisual) {
596
+ hostingVisual.Children().InsertAtTop(
597
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
598
+ m_focusPrimitive->m_focusVisual));
599
+ } else {
600
+ assert(
601
+ view.get() ==
602
+ this); // When not using lifted comp, focus visuals should always host within their own component
603
+ OuterVisual().InsertAt(m_focusPrimitive->m_focusVisual, 1);
604
+ }
1136
605
  }
1137
- } else {
1138
- facebook::react::RectangleEdges<float> rectPathGeometry = {0, 0, extentWidth, extentHeight};
1139
-
1140
- winrt::com_ptr<ID2D1GeometryGroup> pathGeometry = GetGeometryForRoundedBorder(
1141
- m_compContext,
1142
- borderMetrics.borderRadii,
1143
- {0, 0, 0, 0}, // inset
1144
- borderMetrics.borderWidths,
1145
- rectPathGeometry);
1146
-
1147
- DrawAllBorderLayers(
1148
- theme,
1149
- m_compContext,
1150
- spBorderVisuals,
1151
- *pathGeometry,
1152
- borderMetrics.borderWidths,
1153
- borderMetrics.borderRadii,
1154
- extentWidth,
1155
- extentHeight,
1156
- borderMetrics.borderColors,
1157
- borderStyle);
1158
- }
1159
- } else {
1160
- // Because in DirectX geometry starts at the center of the stroke, we need to deflate rectangle by half the stroke
1161
- // width / height to render correctly.
1162
- D2D1_RECT_F rectShape{
1163
- borderMetrics.borderWidths.left / 2.0f,
1164
- borderMetrics.borderWidths.top / 2.0f,
1165
- extentWidth - (borderMetrics.borderWidths.right / 2.0f),
1166
- extentHeight - (borderMetrics.borderWidths.bottom / 2.0f)};
1167
- DrawAllBorderLayers(
1168
- theme,
1169
- m_compContext,
1170
- spBorderVisuals,
1171
- rectShape,
1172
- borderMetrics.borderWidths,
1173
- borderMetrics.borderRadii,
1174
- extentWidth,
1175
- extentHeight,
1176
- borderMetrics.borderColors,
1177
- borderStyle);
1178
- }
1179
- return true;
1180
- }
1181
-
1182
- void ComponentView::finalizeBorderUpdates(
1183
- facebook::react::LayoutMetrics const &layoutMetrics,
1184
- const facebook::react::ViewProps &viewProps) noexcept {
1185
- if (!m_needsBorderUpdate || theme()->IsEmpty()) {
1186
- return;
1187
- }
1188
-
1189
- m_needsBorderUpdate = false;
1190
- auto spBorderLayers = FindSpecialBorderLayers();
1191
606
 
1192
- if (!TryUpdateSpecialBorderLayers(theme(), spBorderLayers, layoutMetrics, viewProps)) {
1193
- for (auto &spBorderLayer : spBorderLayers) {
1194
- if (spBorderLayer) {
1195
- spBorderLayer.as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Brush(nullptr);
607
+ m_focusPrimitive->m_focusVisual.IsVisible(true);
608
+ assert(view->viewProps()->enableFocusRing);
609
+ if (!m_focusPrimitive->m_focusInnerPrimitive) {
610
+ m_focusPrimitive->m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
611
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusInnerPrimitive->RootVisual(), 0);
1196
612
  }
613
+ if (!m_focusPrimitive->m_focusOuterPrimitive) {
614
+ m_focusPrimitive->m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
615
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusOuterPrimitive->RootVisual(), 0);
616
+ }
617
+ if (auto focusVisual =
618
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
619
+ m_focusPrimitive->m_focusVisual)) {
620
+ auto outerVisual =
621
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
622
+ view->OuterVisual());
623
+ focusVisual.ParentForTransform(outerVisual);
624
+ }
625
+ updateFocusLayoutMetrics();
626
+ auto innerFocusMetrics = view->focusLayoutMetrics(true /*inner*/);
627
+ m_focusPrimitive->m_focusInnerPrimitive->finalize(
628
+ innerFocusMetrics, view->focusBorderMetrics(true /*inner*/, innerFocusMetrics));
629
+ auto outerFocusMetrics = view->focusLayoutMetrics(false /*inner*/);
630
+ m_focusPrimitive->m_focusOuterPrimitive->finalize(
631
+ outerFocusMetrics, view->focusBorderMetrics(false /*inner*/, outerFocusMetrics));
632
+ } else if (!show && view->m_componentHostingFocusVisual && m_focusPrimitive) {
633
+ m_focusPrimitive->m_focusVisualComponent = nullptr;
634
+ m_focusPrimitive->m_focusVisual.IsVisible(false);
635
+ view->m_componentHostingFocusVisual = nullptr;
1197
636
  }
1198
637
  }
1199
638
  }
1200
639
 
1201
- winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::OuterVisual() const noexcept {
1202
- return m_outerVisual ? m_outerVisual : Visual();
1203
- }
1204
-
1205
- void ComponentView::showFocusVisual(bool show) noexcept {
1206
- if (show) {
1207
- assert(m_enableFocusVisual);
1208
- m_focusVisual.IsFocused(true);
1209
- } else {
1210
- m_focusVisual.IsFocused(false);
1211
- }
1212
- }
1213
-
1214
- void ComponentView::updateBorderProps(
1215
- const facebook::react::ViewProps &oldViewProps,
1216
- const facebook::react::ViewProps &newViewProps) noexcept {
1217
- if (oldViewProps.borderColors != newViewProps.borderColors || oldViewProps.borderRadii != newViewProps.borderRadii ||
1218
- !(oldViewProps.yogaStyle.border(facebook::yoga::Edge::All) ==
1219
- newViewProps.yogaStyle.border(facebook::yoga::Edge::All)) ||
1220
- oldViewProps.borderStyles != newViewProps.borderStyles) {
1221
- m_needsBorderUpdate = true;
1222
- }
1223
-
1224
- m_enableFocusVisual = newViewProps.enableFocusRing;
1225
- if (!m_enableFocusVisual) {
1226
- showFocusVisual(false);
1227
- }
1228
- }
1229
-
1230
640
  void ComponentView::updateShadowProps(
1231
641
  const facebook::react::ViewProps &oldViewProps,
1232
642
  const facebook::react::ViewProps &newViewProps) noexcept {
@@ -1240,11 +650,13 @@ void ComponentView::updateShadowProps(
1240
650
 
1241
651
  void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps) noexcept {
1242
652
  auto shadow = m_compContext.CreateDropShadow();
653
+
1243
654
  shadow.Offset({viewProps.shadowOffset.width, viewProps.shadowOffset.height, 0});
1244
655
  shadow.Opacity(viewProps.shadowOpacity);
1245
656
  shadow.BlurRadius(viewProps.shadowRadius);
1246
657
  if (viewProps.shadowColor)
1247
658
  shadow.Color(theme()->Color(*viewProps.shadowColor));
659
+
1248
660
  Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
1249
661
  }
1250
662
 
@@ -1357,16 +769,16 @@ void ComponentView::Toggle() noexcept {
1357
769
  // no-op
1358
770
  }
1359
771
 
1360
- void ComponentView::updateBorderLayoutMetrics(
772
+ void ComponentView::updateClippingPath(
1361
773
  facebook::react::LayoutMetrics const &layoutMetrics,
1362
774
  const facebook::react::ViewProps &viewProps) noexcept {
1363
- auto borderMetrics = resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
775
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1364
776
 
1365
777
  if (borderMetrics.borderRadii.topLeft == 0 && borderMetrics.borderRadii.topRight == 0 &&
1366
778
  borderMetrics.borderRadii.bottomLeft == 0 && borderMetrics.borderRadii.bottomRight == 0) {
1367
779
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
1368
780
  } else {
1369
- winrt::com_ptr<ID2D1PathGeometry> pathGeometry = GenerateRoundedRectPathGeometry(
781
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
1370
782
  m_compContext,
1371
783
  borderMetrics.borderRadii,
1372
784
  {0, 0, 0, 0},
@@ -1378,24 +790,16 @@ void ComponentView::updateBorderLayoutMetrics(
1378
790
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1379
791
  pathGeometry.get());
1380
792
  }
793
+ }
1381
794
 
1382
- if (m_layoutMetrics != layoutMetrics) {
1383
- m_needsBorderUpdate = true;
1384
- }
1385
-
1386
- m_focusVisual.ScaleFactor(layoutMetrics.pointScaleFactor);
1387
- OuterVisual().Size(
1388
- {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
1389
- layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
1390
- OuterVisual().Offset({
1391
- layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
1392
- layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
1393
- 0.0f,
1394
- });
795
+ std::pair<facebook::react::Cursor, HCURSOR> ComponentView::cursor() const noexcept {
796
+ return {viewProps()->cursor, nullptr};
1395
797
  }
1396
798
 
1397
799
  void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
1398
- index += m_numBorderVisuals;
800
+ if (m_borderPrimitive) {
801
+ index += m_borderPrimitive->numberOfVisuals();
802
+ }
1399
803
  }
1400
804
 
1401
805
  void ComponentView::OnRenderingDeviceLost() noexcept {}
@@ -1513,24 +917,15 @@ ViewComponentView::ViewComponentView(
1513
917
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
1514
918
  facebook::react::Tag tag,
1515
919
  winrt::Microsoft::ReactNative::ReactContext const &reactContext,
1516
- ComponentViewFeatures flags)
1517
- : base_type(compContext, tag, reactContext, flags),
920
+ ComponentViewFeatures flags,
921
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder)
922
+ : base_type(compContext, tag, reactContext, flags, builder),
1518
923
  m_props(defaultProps ? defaultProps : ViewComponentView::defaultProps()) {}
1519
924
 
1520
925
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ViewComponentView::createVisual() noexcept {
1521
926
  return m_compContext.CreateSpriteVisual();
1522
927
  }
1523
928
 
1524
- void ViewComponentView::CreateVisualHandler(
1525
- const winrt::Microsoft::ReactNative::Composition::CreateVisualDelegate &handler) {
1526
- m_createVisualHandler = handler;
1527
- }
1528
-
1529
- winrt::Microsoft::ReactNative::Composition::CreateVisualDelegate ViewComponentView::CreateVisualHandler()
1530
- const noexcept {
1531
- return m_createVisualHandler;
1532
- }
1533
-
1534
929
  void ViewComponentView::CreateInternalVisualHandler(
1535
930
  const winrt::Microsoft::ReactNative::Composition::Experimental::CreateInternalVisualDelegate &handler) {
1536
931
  m_createInternalVisualHandler = handler;
@@ -1545,10 +940,10 @@ void ViewComponentView::ensureVisual() noexcept {
1545
940
  if (!m_visual) {
1546
941
  if (m_createInternalVisualHandler) {
1547
942
  m_visual = m_createInternalVisualHandler(*this);
1548
- } else if (m_createVisualHandler) {
943
+ } else if (m_builder && m_builder->CreateVisualHandler()) {
1549
944
  m_visual =
1550
945
  winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::CreateVisual(
1551
- m_createVisualHandler(*this));
946
+ m_builder->CreateVisualHandler()(*this));
1552
947
  } else {
1553
948
  m_visual = createVisual();
1554
949
  }
@@ -1564,6 +959,13 @@ winrt::Microsoft::ReactNative::ComponentView ViewComponentView::Create(
1564
959
  ViewComponentView::defaultProps(), compContext, tag, reactContext, ComponentViewFeatures::Default);
1565
960
  }
1566
961
 
962
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
963
+ ViewComponentView::VisualToMountChildrenInto() noexcept {
964
+ if (m_builder && m_builder->VisualToMountChildrenIntoHandler())
965
+ return m_builder->VisualToMountChildrenIntoHandler()(*this);
966
+ return Visual();
967
+ }
968
+
1567
969
  void ViewComponentView::MountChildComponentView(
1568
970
  const winrt::Microsoft::ReactNative::ComponentView &childComponentView,
1569
971
  uint32_t index) noexcept {
@@ -1583,7 +985,7 @@ void ViewComponentView::MountChildComponentView(
1583
985
  }
1584
986
  }
1585
987
  }
1586
- Visual().InsertAt(compositionChild->OuterVisual(), visualIndex);
988
+ VisualToMountChildrenInto().InsertAt(compositionChild->OuterVisual(), visualIndex);
1587
989
  } else {
1588
990
  m_hasNonVisualChildren = true;
1589
991
  }
@@ -1596,7 +998,7 @@ void ViewComponentView::UnmountChildComponentView(
1596
998
 
1597
999
  indexOffsetForBorder(index);
1598
1000
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1599
- Visual().Remove(compositionChild->OuterVisual());
1001
+ VisualToMountChildrenInto().Remove(compositionChild->OuterVisual());
1600
1002
  }
1601
1003
  }
1602
1004
 
@@ -1808,7 +1210,7 @@ winrt::Microsoft::ReactNative::ViewProps ViewComponentView::ViewProps() noexcept
1808
1210
 
1809
1211
  winrt::Microsoft::ReactNative::ViewProps ViewComponentView::ViewPropsInner() noexcept {
1810
1212
  // If we have AbiViewProps, then we dont need to new up a props wrapper
1811
- if (m_customComponent) {
1213
+ if (m_builder) {
1812
1214
  const auto &abiViewProps = *std::static_pointer_cast<const ::Microsoft::ReactNative::AbiViewProps>(m_props);
1813
1215
  return abiViewProps.ViewProps();
1814
1216
  }
@@ -1898,6 +1300,9 @@ winrt::Microsoft::ReactNative::ComponentView lastDeepChild(
1898
1300
  return current;
1899
1301
  }
1900
1302
 
1303
+ // Walks the tree calling the function fn on each node.
1304
+ // If fn returns true, then walkTree stops itterating over the tree, and returns true.
1305
+ // If the tree walk completes without fn returning true, then walkTree returns false.
1901
1306
  bool walkTree(
1902
1307
  const winrt::Microsoft::ReactNative::ComponentView &view,
1903
1308
  bool forward,