react-native-windows 0.75.9 → 0.75.10

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 (49) hide show
  1. package/Microsoft.ReactNative/CompositionComponentView.idl +2 -1
  2. package/Microsoft.ReactNative/Fabric/AbiComponentDescriptor.cpp +4 -1
  3. package/Microsoft.ReactNative/Fabric/AbiViewComponentDescriptor.cpp +1 -1
  4. package/Microsoft.ReactNative/Fabric/AbiViewProps.cpp +7 -0
  5. package/Microsoft.ReactNative/Fabric/AbiViewProps.h +2 -0
  6. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +19 -4
  7. package/Microsoft.ReactNative/Fabric/ComponentView.h +8 -3
  8. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +931 -0
  9. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +80 -0
  10. package/Microsoft.ReactNative/Fabric/Composition/CompositionDynamicAutomationProvider.cpp +31 -13
  11. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +27 -3
  12. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +3 -1
  13. package/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +8 -32
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +302 -895
  15. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +26 -26
  16. package/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp +1 -1
  17. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +130 -122
  18. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +14 -8
  19. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +34 -20
  20. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +5 -3
  21. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +63 -2
  22. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +12 -0
  23. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +51 -3
  24. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +3 -0
  25. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +18 -8
  26. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +3 -0
  27. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +46 -4
  28. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +5 -0
  29. package/Microsoft.ReactNative/Fabric/Composition/Theme.cpp +9 -3
  30. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +11 -0
  31. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +2 -0
  32. package/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +1 -1
  33. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +1 -0
  34. package/Microsoft.ReactNative/IReactViewComponentBuilder.idl +1 -1
  35. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +25 -129
  36. package/Microsoft.ReactNative/ReactNativeAppBuilder.h +5 -13
  37. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +13 -34
  38. package/Microsoft.ReactNative/ReactNativeIsland.idl +3 -2
  39. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +129 -18
  40. package/Microsoft.ReactNative/ReactNativeWin32App.h +14 -5
  41. package/Microsoft.ReactNative/ViewProps.idl +2 -0
  42. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +14 -0
  43. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  44. package/Shared/Shared.vcxitems +3 -10
  45. package/Shared/Shared.vcxitems.filters +1 -0
  46. package/package.json +3 -3
  47. package/templates/cpp-app/windows/MyApp/MyApp.cpp +46 -130
  48. package/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp +0 -59
  49. package/Microsoft.ReactNative/ReactInstanceSettingsBuilder.h +0 -23
@@ -32,6 +32,23 @@
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,
@@ -40,8 +57,6 @@ ComponentView::ComponentView(
40
57
  : base_type(tag, reactContext), m_compContext(compContext), m_flags(flags) {
41
58
  m_outerVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
42
59
  // CreateContainerVisual in ICompositionContext
43
- m_focusVisual = compContext.CreateFocusVisual();
44
- m_outerVisual.InsertAt(m_focusVisual.InnerVisual(), 0);
45
60
  }
46
61
 
47
62
  ComponentView::~ComponentView() {
@@ -68,9 +83,21 @@ void ComponentView::onThemeChanged() noexcept {
68
83
  }
69
84
  }
70
85
 
71
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
72
- m_needsBorderUpdate = true;
73
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
86
+ if (m_borderPrimitive) {
87
+ m_borderPrimitive->onThemeChanged(
88
+ m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps()));
89
+ }
90
+ if (m_componentHostingFocusVisual) {
91
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
92
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
93
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->onThemeChanged(
94
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
95
+ }
96
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
97
+ auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
98
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->onThemeChanged(
99
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
100
+ }
74
101
  }
75
102
 
76
103
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
@@ -131,13 +158,25 @@ void ComponentView::updateProps(
131
158
  }
132
159
  }
133
160
 
134
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
135
- updateBorderProps(oldViewProps, newViewProps);
161
+ if (m_borderPrimitive) {
162
+ m_borderPrimitive->updateProps(oldViewProps, newViewProps);
163
+ }
164
+
165
+ if (m_componentHostingFocusVisual) {
166
+ if (!newViewProps.enableFocusRing) {
167
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
168
+ }
169
+
170
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
171
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
172
+ }
173
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
174
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
175
+ }
136
176
  }
137
177
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
138
178
  updateShadowProps(oldViewProps, newViewProps);
139
179
  }
140
-
141
180
  if (oldViewProps.tooltip != newViewProps.tooltip) {
142
181
  if (!m_tooltipTracked && newViewProps.tooltip) {
143
182
  TooltipService::GetCurrent(m_reactContext.Properties())->StartTracking(*this);
@@ -155,13 +194,84 @@ void ComponentView::updateLayoutMetrics(
155
194
  facebook::react::LayoutMetrics const &layoutMetrics,
156
195
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
157
196
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
158
- updateBorderLayoutMetrics(layoutMetrics, *viewProps());
197
+ updateClippingPath(layoutMetrics, *viewProps());
198
+ OuterVisual().Size(
199
+ {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
200
+ layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
201
+ OuterVisual().Offset({
202
+ layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
203
+ layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
204
+ 0.0f,
205
+ });
159
206
  }
160
207
 
161
208
  base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
209
+
210
+ if (layoutMetrics != oldLayoutMetrics) {
211
+ if (m_borderPrimitive) {
212
+ m_borderPrimitive->markNeedsUpdate();
213
+ }
214
+
215
+ if (m_componentHostingFocusVisual) {
216
+ m_componentHostingFocusVisual->updateFocusLayoutMetrics();
217
+ }
218
+ }
219
+
162
220
  UpdateCenterPropertySet();
163
221
  }
164
222
 
223
+ void ComponentView::updateFocusLayoutMetrics() noexcept {
224
+ facebook::react::RectangleEdges<bool> nudgeEdges;
225
+ auto scaleFactor = m_focusPrimitive->m_focusVisualComponent->m_layoutMetrics.pointScaleFactor;
226
+ if (m_focusPrimitive) {
227
+ if (m_focusPrimitive->m_focusOuterPrimitive) {
228
+ auto outerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(false /*inner*/);
229
+
230
+ if (outerFocusMetrics.frame.origin.x < 0) {
231
+ nudgeEdges.left = true;
232
+ }
233
+ if (outerFocusMetrics.frame.origin.y < 0) {
234
+ nudgeEdges.top = true;
235
+ }
236
+ if (outerFocusMetrics.frame.getMaxX() > m_layoutMetrics.frame.getMaxX()) {
237
+ nudgeEdges.right = true;
238
+ }
239
+ if (outerFocusMetrics.frame.getMaxY() > m_layoutMetrics.frame.getMaxY()) {
240
+ nudgeEdges.bottom = true;
241
+ }
242
+
243
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Size(
244
+ {outerFocusMetrics.frame.size.width * scaleFactor -
245
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
246
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0),
247
+ outerFocusMetrics.frame.size.height * scaleFactor -
248
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
249
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0)});
250
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Offset(
251
+ {nudgeEdges.left ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
252
+ nudgeEdges.top ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
253
+ 0.0f});
254
+ m_focusPrimitive->m_focusOuterPrimitive->markNeedsUpdate();
255
+ }
256
+
257
+ if (m_focusPrimitive->m_focusInnerPrimitive) {
258
+ auto innerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(true /*inner*/);
259
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Size(
260
+ {innerFocusMetrics.frame.size.width * scaleFactor -
261
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
262
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0),
263
+ innerFocusMetrics.frame.size.height * scaleFactor -
264
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
265
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0)});
266
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Offset(
267
+ {nudgeEdges.left ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
268
+ nudgeEdges.top ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
269
+ 0.0f});
270
+ m_focusPrimitive->m_focusInnerPrimitive->markNeedsUpdate();
271
+ }
272
+ }
273
+ }
274
+
165
275
  const facebook::react::LayoutMetrics &ComponentView::layoutMetrics() const noexcept {
166
276
  return m_layoutMetrics;
167
277
  }
@@ -199,7 +309,27 @@ void ComponentView::FinalizeTransform(
199
309
 
200
310
  void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept {
201
311
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
202
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
312
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps());
313
+ if (!m_borderPrimitive && BorderPrimitive::requiresBorder(borderMetrics, theme())) {
314
+ m_borderPrimitive = std::make_shared<BorderPrimitive>(*this, Visual());
315
+ }
316
+
317
+ if (m_borderPrimitive) {
318
+ m_borderPrimitive->finalize(m_layoutMetrics, borderMetrics);
319
+ }
320
+ }
321
+
322
+ if (m_componentHostingFocusVisual) {
323
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
324
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
325
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->finalize(
326
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
327
+ }
328
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
329
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
330
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->finalize(
331
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
332
+ }
203
333
  }
204
334
 
205
335
  if (m_FinalizeTransform) {
@@ -213,7 +343,12 @@ void ComponentView::onLostFocus(
213
343
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
214
344
  if (args.OriginalSource() == Tag()) {
215
345
  m_eventEmitter->onBlur();
216
- showFocusVisual(false);
346
+
347
+ if (m_componentHostingFocusVisual) {
348
+ auto s = get_strong();
349
+
350
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
351
+ }
217
352
  if (m_uiaProvider) {
218
353
  winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
219
354
  m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false);
@@ -222,12 +357,47 @@ void ComponentView::onLostFocus(
222
357
  base_type::onLostFocus(args);
223
358
  }
224
359
 
360
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::visualToHostFocus() noexcept {
361
+ return OuterVisual();
362
+ }
363
+
364
+ // We want to host focus visuals as close to the focused component as possible. However since the focus visuals extend
365
+ // past the bounds of the component, in cases where additional components are positioned directly next to this one, we'd
366
+ // 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
367
+ // bounds will fix the entire focus rect.
368
+ winrt::com_ptr<ComponentView> ComponentView::focusVisualRoot(const facebook::react::Rect &focusRect) noexcept {
369
+ auto compVisual =
370
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(OuterVisual());
371
+ if (!compVisual) {
372
+ return get_strong();
373
+ // When not using lifted composition, force the focus visual to host within its own component, as we do not support
374
+ // ParentForTransform
375
+ }
376
+
377
+ if (facebook::react::Rect::intersect(focusRect, m_layoutMetrics.frame) == focusRect) {
378
+ return get_strong();
379
+ }
380
+
381
+ if (!m_parent) {
382
+ return get_strong();
383
+ }
384
+
385
+ return m_parent.as<ComponentView>()->focusVisualRoot(
386
+ {{focusRect.origin.x + m_layoutMetrics.frame.origin.x, focusRect.origin.y + m_layoutMetrics.frame.origin.y},
387
+ focusRect.size});
388
+ }
389
+
225
390
  void ComponentView::onGotFocus(
226
391
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
227
392
  if (args.OriginalSource() == Tag()) {
228
393
  m_eventEmitter->onFocus();
229
- if (m_enableFocusVisual) {
230
- showFocusVisual(true);
394
+ if (viewProps()->enableFocusRing) {
395
+ facebook::react::Rect focusRect = m_layoutMetrics.frame;
396
+ focusRect.origin.x -= (FOCUS_VISUAL_WIDTH * 2);
397
+ focusRect.origin.y -= (FOCUS_VISUAL_WIDTH * 2);
398
+ focusRect.size.width += (FOCUS_VISUAL_WIDTH * 2);
399
+ focusRect.size.height += (FOCUS_VISUAL_WIDTH * 2);
400
+ focusVisualRoot(focusRect)->hostFocusVisual(true, get_strong());
231
401
  }
232
402
  if (m_uiaProvider) {
233
403
  auto spProviderSimple = m_uiaProvider.try_as<IRawElementProviderSimple>();
@@ -325,6 +495,10 @@ void ComponentView::ReleasePointerCapture(
325
495
  ->ReleasePointerCapture(pointer, static_cast<facebook::react::Tag>(Tag()));
326
496
  }
327
497
 
498
+ void ComponentView::SetViewFeatures(ComponentViewFeatures viewFeatures) noexcept {
499
+ m_flags = viewFeatures;
500
+ }
501
+
328
502
  RECT ComponentView::getClientRect() const noexcept {
329
503
  RECT rc{0};
330
504
  facebook::react::Point parentOffset{0};
@@ -356,877 +530,112 @@ const facebook::react::SharedViewEventEmitter &ComponentView::GetEventEmitter()
356
530
  return m_eventEmitter;
357
531
  }
358
532
 
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;
533
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::OuterVisual() const noexcept {
534
+ return m_outerVisual ? m_outerVisual : Visual();
1076
535
  }
1077
536
 
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);
537
+ facebook::react::LayoutMetrics ComponentView::focusLayoutMetrics(bool inner) const noexcept {
538
+ facebook::react::LayoutMetrics layoutMetrics = m_layoutMetrics;
539
+ layoutMetrics.frame.origin.x -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
540
+ layoutMetrics.frame.origin.y -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
541
+ layoutMetrics.frame.size.height += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
542
+ layoutMetrics.frame.size.width += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
543
+ return layoutMetrics;
544
+ }
545
+
546
+ facebook::react::BorderMetrics ComponentView::focusBorderMetrics(
547
+ bool inner,
548
+ const facebook::react::LayoutMetrics &layoutMetrics) const noexcept {
549
+ facebook::react::BorderMetrics metrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, *viewProps());
550
+ facebook::react::Color innerColor;
551
+ innerColor.m_color = {1, 0, 0, 0};
552
+ innerColor.m_platformColor.push_back(inner ? "FocusVisualSecondary" : "FocusVisualPrimary");
553
+ metrics.borderColors.bottom = metrics.borderColors.left = metrics.borderColors.right = metrics.borderColors.top =
554
+ innerColor;
555
+ if (metrics.borderRadii.bottomLeft != 0)
556
+ metrics.borderRadii.bottomLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
557
+ if (metrics.borderRadii.bottomRight != 0)
558
+ metrics.borderRadii.bottomRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
559
+ if (metrics.borderRadii.topLeft != 0)
560
+ metrics.borderRadii.topLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
561
+ if (metrics.borderRadii.topRight != 0)
562
+ metrics.borderRadii.topRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
563
+
564
+ metrics.borderStyles.bottom = metrics.borderStyles.left = metrics.borderStyles.right = metrics.borderStyles.top =
565
+ facebook::react::BorderStyle::Solid;
566
+ metrics.borderWidths.bottom = metrics.borderWidths.left = metrics.borderWidths.right = metrics.borderWidths.top =
567
+ FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor;
568
+ return metrics;
569
+ }
570
+
571
+ void ComponentView::hostFocusVisual(bool show, winrt::com_ptr<ComponentView> view) noexcept {
572
+ if ((view->m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
573
+ // Any previous view showing focus visuals should have removed them before another shows it
574
+ assert(
575
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
576
+ m_focusPrimitive->m_focusVisualComponent == view);
577
+ assert(
578
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
579
+ view->m_componentHostingFocusVisual.get() == this);
580
+ if (show && !view->m_componentHostingFocusVisual) {
581
+ view->m_componentHostingFocusVisual = get_strong();
582
+
583
+ if (!m_focusPrimitive) {
584
+ m_focusPrimitive = std::make_unique<FocusPrimitive>();
585
+ }
586
+ m_focusPrimitive->m_focusVisualComponent = view;
587
+
588
+ if (!m_focusPrimitive->m_focusVisual) {
589
+ m_focusPrimitive->m_focusVisual = m_compContext.CreateSpriteVisual();
590
+ auto hostingVisual =
591
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
592
+ visualToHostFocus())
593
+ .as<winrt::Microsoft::UI::Composition::ContainerVisual>();
594
+ if (hostingVisual) {
595
+ hostingVisual.Children().InsertAtTop(
596
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
597
+ m_focusPrimitive->m_focusVisual));
598
+ } else {
599
+ assert(
600
+ view.get() ==
601
+ this); // When not using lifted comp, focus visuals should always host within their own component
602
+ OuterVisual().InsertAt(m_focusPrimitive->m_focusVisual, 1);
603
+ }
1136
604
  }
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
605
 
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);
606
+ m_focusPrimitive->m_focusVisual.IsVisible(true);
607
+ assert(view->viewProps()->enableFocusRing);
608
+ if (!m_focusPrimitive->m_focusInnerPrimitive) {
609
+ m_focusPrimitive->m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
610
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusInnerPrimitive->RootVisual(), 0);
611
+ }
612
+ if (!m_focusPrimitive->m_focusOuterPrimitive) {
613
+ m_focusPrimitive->m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
614
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusOuterPrimitive->RootVisual(), 0);
615
+ }
616
+ if (auto focusVisual =
617
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
618
+ m_focusPrimitive->m_focusVisual)) {
619
+ auto outerVisual =
620
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
621
+ view->OuterVisual());
622
+ focusVisual.ParentForTransform(outerVisual);
1196
623
  }
624
+ updateFocusLayoutMetrics();
625
+ auto innerFocusMetrics = view->focusLayoutMetrics(true /*inner*/);
626
+ m_focusPrimitive->m_focusInnerPrimitive->finalize(
627
+ innerFocusMetrics, view->focusBorderMetrics(true /*inner*/, innerFocusMetrics));
628
+ auto outerFocusMetrics = view->focusLayoutMetrics(false /*inner*/);
629
+ m_focusPrimitive->m_focusOuterPrimitive->finalize(
630
+ outerFocusMetrics, view->focusBorderMetrics(false /*inner*/, outerFocusMetrics));
631
+ } else if (!show && view->m_componentHostingFocusVisual && m_focusPrimitive) {
632
+ m_focusPrimitive->m_focusVisualComponent = nullptr;
633
+ m_focusPrimitive->m_focusVisual.IsVisible(false);
634
+ view->m_componentHostingFocusVisual = nullptr;
1197
635
  }
1198
636
  }
1199
637
  }
1200
638
 
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
639
  void ComponentView::updateShadowProps(
1231
640
  const facebook::react::ViewProps &oldViewProps,
1232
641
  const facebook::react::ViewProps &newViewProps) noexcept {
@@ -1240,11 +649,13 @@ void ComponentView::updateShadowProps(
1240
649
 
1241
650
  void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps) noexcept {
1242
651
  auto shadow = m_compContext.CreateDropShadow();
652
+
1243
653
  shadow.Offset({viewProps.shadowOffset.width, viewProps.shadowOffset.height, 0});
1244
654
  shadow.Opacity(viewProps.shadowOpacity);
1245
655
  shadow.BlurRadius(viewProps.shadowRadius);
1246
656
  if (viewProps.shadowColor)
1247
657
  shadow.Color(theme()->Color(*viewProps.shadowColor));
658
+
1248
659
  Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
1249
660
  }
1250
661
 
@@ -1357,16 +768,16 @@ void ComponentView::Toggle() noexcept {
1357
768
  // no-op
1358
769
  }
1359
770
 
1360
- void ComponentView::updateBorderLayoutMetrics(
771
+ void ComponentView::updateClippingPath(
1361
772
  facebook::react::LayoutMetrics const &layoutMetrics,
1362
773
  const facebook::react::ViewProps &viewProps) noexcept {
1363
- auto borderMetrics = resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
774
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1364
775
 
1365
776
  if (borderMetrics.borderRadii.topLeft == 0 && borderMetrics.borderRadii.topRight == 0 &&
1366
777
  borderMetrics.borderRadii.bottomLeft == 0 && borderMetrics.borderRadii.bottomRight == 0) {
1367
778
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
1368
779
  } else {
1369
- winrt::com_ptr<ID2D1PathGeometry> pathGeometry = GenerateRoundedRectPathGeometry(
780
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
1370
781
  m_compContext,
1371
782
  borderMetrics.borderRadii,
1372
783
  {0, 0, 0, 0},
@@ -1378,24 +789,12 @@ void ComponentView::updateBorderLayoutMetrics(
1378
789
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1379
790
  pathGeometry.get());
1380
791
  }
1381
-
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
- });
1395
792
  }
1396
793
 
1397
794
  void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
1398
- index += m_numBorderVisuals;
795
+ if (m_borderPrimitive) {
796
+ index += m_borderPrimitive->numberOfVisuals();
797
+ }
1399
798
  }
1400
799
 
1401
800
  void ComponentView::OnRenderingDeviceLost() noexcept {}
@@ -1564,6 +963,11 @@ winrt::Microsoft::ReactNative::ComponentView ViewComponentView::Create(
1564
963
  ViewComponentView::defaultProps(), compContext, tag, reactContext, ComponentViewFeatures::Default);
1565
964
  }
1566
965
 
966
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
967
+ ViewComponentView::VisualToMountChildrenInto() noexcept {
968
+ return Visual();
969
+ }
970
+
1567
971
  void ViewComponentView::MountChildComponentView(
1568
972
  const winrt::Microsoft::ReactNative::ComponentView &childComponentView,
1569
973
  uint32_t index) noexcept {
@@ -1583,7 +987,7 @@ void ViewComponentView::MountChildComponentView(
1583
987
  }
1584
988
  }
1585
989
  }
1586
- Visual().InsertAt(compositionChild->OuterVisual(), visualIndex);
990
+ VisualToMountChildrenInto().InsertAt(compositionChild->OuterVisual(), visualIndex);
1587
991
  } else {
1588
992
  m_hasNonVisualChildren = true;
1589
993
  }
@@ -1596,7 +1000,7 @@ void ViewComponentView::UnmountChildComponentView(
1596
1000
 
1597
1001
  indexOffsetForBorder(index);
1598
1002
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1599
- Visual().Remove(compositionChild->OuterVisual());
1003
+ VisualToMountChildrenInto().Remove(compositionChild->OuterVisual());
1600
1004
  }
1601
1005
  }
1602
1006
 
@@ -1898,6 +1302,9 @@ winrt::Microsoft::ReactNative::ComponentView lastDeepChild(
1898
1302
  return current;
1899
1303
  }
1900
1304
 
1305
+ // Walks the tree calling the function fn on each node.
1306
+ // If fn returns true, then walkTree stops itterating over the tree, and returns true.
1307
+ // If the tree walk completes without fn returning true, then walkTree returns false.
1901
1308
  bool walkTree(
1902
1309
  const winrt::Microsoft::ReactNative::ComponentView &view,
1903
1310
  bool forward,