react-native-windows 0.74.23 → 0.74.24

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 (44) 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 +11 -4
  7. package/Microsoft.ReactNative/Fabric/ComponentView.h +3 -2
  8. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +926 -0
  9. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +76 -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/CompositionViewComponentView.cpp +193 -892
  14. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +17 -22
  15. package/Microsoft.ReactNative/Fabric/Composition/DebuggingOverlayComponentView.cpp +1 -1
  16. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +129 -122
  17. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +14 -8
  18. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +34 -20
  19. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +5 -3
  20. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +40 -2
  21. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +9 -0
  22. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +31 -3
  23. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +8 -8
  24. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +31 -1
  25. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +4 -0
  26. package/Microsoft.ReactNative/Fabric/Composition/Theme.cpp +9 -3
  27. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +11 -0
  28. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.h +2 -0
  29. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +1 -0
  30. package/Microsoft.ReactNative/IReactViewComponentBuilder.idl +1 -1
  31. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +25 -129
  32. package/Microsoft.ReactNative/ReactNativeAppBuilder.h +5 -13
  33. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +13 -34
  34. package/Microsoft.ReactNative/ReactNativeIsland.idl +3 -2
  35. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +129 -18
  36. package/Microsoft.ReactNative/ReactNativeWin32App.h +14 -5
  37. package/Microsoft.ReactNative/ViewProps.idl +2 -0
  38. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  39. package/Shared/Shared.vcxitems +3 -10
  40. package/Shared/Shared.vcxitems.filters +1 -0
  41. package/package.json +3 -3
  42. package/templates/cpp-app/windows/MyApp/MyApp.cpp +46 -130
  43. package/Microsoft.ReactNative/ReactInstanceSettingsBuilder.cpp +0 -59
  44. 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
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,9 @@ 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);
60
+ m_focusVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
61
+ // CreateContainerVisual in ICompositionContext
62
+ m_outerVisual.InsertAt(m_focusVisual, 0);
45
63
  }
46
64
 
47
65
  ComponentView::~ComponentView() {
@@ -68,9 +86,17 @@ void ComponentView::onThemeChanged() noexcept {
68
86
  }
69
87
  }
70
88
 
71
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
72
- m_needsBorderUpdate = true;
73
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
89
+ if (m_borderPrimitive) {
90
+ m_borderPrimitive->onThemeChanged(
91
+ m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps()));
92
+ }
93
+ if (m_focusInnerPrimitive) {
94
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
95
+ m_focusInnerPrimitive->onThemeChanged(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
96
+ }
97
+ if (m_focusOuterPrimitive) {
98
+ auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
99
+ m_focusOuterPrimitive->onThemeChanged(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
74
100
  }
75
101
 
76
102
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
@@ -131,13 +157,21 @@ void ComponentView::updateProps(
131
157
  }
132
158
  }
133
159
 
134
- if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
135
- updateBorderProps(oldViewProps, newViewProps);
160
+ if (m_borderPrimitive) {
161
+ m_borderPrimitive->updateProps(oldViewProps, newViewProps);
162
+ }
163
+ if (!newViewProps.enableFocusRing) {
164
+ showFocusVisual(false);
165
+ }
166
+ if (m_focusInnerPrimitive) {
167
+ m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
168
+ }
169
+ if (m_focusOuterPrimitive) {
170
+ m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
136
171
  }
137
172
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
138
173
  updateShadowProps(oldViewProps, newViewProps);
139
174
  }
140
-
141
175
  if (oldViewProps.tooltip != newViewProps.tooltip) {
142
176
  if (!m_tooltipTracked && newViewProps.tooltip) {
143
177
  TooltipService::GetCurrent(m_reactContext.Properties())->StartTracking(*this);
@@ -155,13 +189,58 @@ void ComponentView::updateLayoutMetrics(
155
189
  facebook::react::LayoutMetrics const &layoutMetrics,
156
190
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
157
191
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
158
- updateBorderLayoutMetrics(layoutMetrics, *viewProps());
192
+ updateClippingPath(layoutMetrics, *viewProps());
193
+ OuterVisual().Size(
194
+ {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
195
+ layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
196
+ OuterVisual().Offset({
197
+ layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
198
+ layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
199
+ 0.0f,
200
+ });
201
+ }
202
+
203
+ updateFocusLayoutMetrics(layoutMetrics);
204
+
205
+ if (layoutMetrics != oldLayoutMetrics) {
206
+ if (m_borderPrimitive) {
207
+ m_borderPrimitive->markNeedsUpdate();
208
+ }
209
+ if (m_focusInnerPrimitive) {
210
+ m_focusInnerPrimitive->markNeedsUpdate();
211
+ }
212
+ if (m_focusOuterPrimitive) {
213
+ m_focusOuterPrimitive->markNeedsUpdate();
214
+ }
159
215
  }
160
216
 
161
217
  base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
162
218
  UpdateCenterPropertySet();
163
219
  }
164
220
 
221
+ void ComponentView::updateFocusLayoutMetrics(facebook::react::LayoutMetrics const &layoutMetrics) noexcept {
222
+ if (m_focusInnerPrimitive) {
223
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
224
+ m_focusInnerPrimitive->RootVisual().Size(
225
+ {innerFocusMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
226
+ innerFocusMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
227
+ m_focusInnerPrimitive->RootVisual().Offset(
228
+ {-FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor,
229
+ -FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor,
230
+ 0.0f});
231
+ }
232
+ if (m_focusOuterPrimitive) {
233
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
234
+ m_focusOuterPrimitive->RootVisual().Size(
235
+ {outerFocusMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
236
+ outerFocusMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
237
+ m_focusOuterPrimitive->RootVisual().Offset(
238
+ {-(FOCUS_VISUAL_WIDTH * 2 * m_layoutMetrics.pointScaleFactor),
239
+ -(FOCUS_VISUAL_WIDTH * 2 * m_layoutMetrics.pointScaleFactor),
240
+ 0.0f});
241
+ }
242
+ }
243
+
165
244
  const facebook::react::LayoutMetrics &ComponentView::layoutMetrics() const noexcept {
166
245
  return m_layoutMetrics;
167
246
  }
@@ -199,7 +278,23 @@ void ComponentView::FinalizeTransform(
199
278
 
200
279
  void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) noexcept {
201
280
  if ((m_flags & ComponentViewFeatures::NativeBorder) == ComponentViewFeatures::NativeBorder) {
202
- finalizeBorderUpdates(m_layoutMetrics, *viewProps());
281
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps());
282
+ if (!m_borderPrimitive && BorderPrimitive::requiresBorder(borderMetrics, theme())) {
283
+ m_borderPrimitive = std::make_shared<BorderPrimitive>(*this, Visual());
284
+ }
285
+
286
+ if (m_borderPrimitive) {
287
+ m_borderPrimitive->finalize(m_layoutMetrics, borderMetrics);
288
+ }
289
+ }
290
+
291
+ if (m_focusInnerPrimitive) {
292
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
293
+ m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
294
+ }
295
+ if (m_focusOuterPrimitive) {
296
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
297
+ m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
203
298
  }
204
299
 
205
300
  if (m_FinalizeTransform) {
@@ -226,7 +321,7 @@ void ComponentView::onGotFocus(
226
321
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
227
322
  if (args.OriginalSource() == Tag()) {
228
323
  m_eventEmitter->onFocus();
229
- if (m_enableFocusVisual) {
324
+ if (viewProps()->enableFocusRing) {
230
325
  showFocusVisual(true);
231
326
  }
232
327
  if (m_uiaProvider) {
@@ -325,6 +420,10 @@ void ComponentView::ReleasePointerCapture(
325
420
  ->ReleasePointerCapture(pointer, static_cast<facebook::react::Tag>(Tag()));
326
421
  }
327
422
 
423
+ void ComponentView::SetViewFeatures(ComponentViewFeatures viewFeatures) noexcept {
424
+ m_flags = viewFeatures;
425
+ }
426
+
328
427
  RECT ComponentView::getClientRect() const noexcept {
329
428
  RECT rc{0};
330
429
  facebook::react::Point parentOffset{0};
@@ -356,877 +455,81 @@ const facebook::react::SharedViewEventEmitter &ComponentView::GetEventEmitter()
356
455
  return m_eventEmitter;
357
456
  }
358
457
 
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
- }
458
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::OuterVisual() const noexcept {
459
+ return m_outerVisual ? m_outerVisual : Visual();
1065
460
  }
1066
461
 
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;
462
+ facebook::react::LayoutMetrics ComponentView::focusLayoutMetrics(bool inner) const noexcept {
463
+ facebook::react::LayoutMetrics layoutMetrics = m_layoutMetrics;
464
+ layoutMetrics.frame.origin.x -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
465
+ layoutMetrics.frame.origin.y -= FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
466
+ layoutMetrics.frame.size.height += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
467
+ layoutMetrics.frame.size.width += FOCUS_VISUAL_WIDTH * (inner ? 2 : 4);
468
+ return layoutMetrics;
469
+ }
470
+
471
+ facebook::react::BorderMetrics ComponentView::focusBorderMetrics(
472
+ bool inner,
473
+ const facebook::react::LayoutMetrics &layoutMetrics) const noexcept {
474
+ facebook::react::BorderMetrics metrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, *viewProps());
475
+ facebook::react::Color innerColor;
476
+ innerColor.m_color = {1, 0, 0, 0};
477
+ innerColor.m_platformColor.push_back(inner ? "FocusVisualSecondary" : "FocusVisualPrimary");
478
+ metrics.borderColors.bottom = metrics.borderColors.left = metrics.borderColors.right = metrics.borderColors.top =
479
+ innerColor;
480
+ if (metrics.borderRadii.bottomLeft != 0)
481
+ metrics.borderRadii.bottomLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
482
+ if (metrics.borderRadii.bottomRight != 0)
483
+ metrics.borderRadii.bottomRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
484
+ if (metrics.borderRadii.topLeft != 0)
485
+ metrics.borderRadii.topLeft += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
486
+ if (metrics.borderRadii.topRight != 0)
487
+ metrics.borderRadii.topRight += FOCUS_VISUAL_WIDTH * (inner ? 1 : 2);
488
+
489
+ metrics.borderStyles.bottom = metrics.borderStyles.left = metrics.borderStyles.right = metrics.borderStyles.top =
490
+ facebook::react::BorderStyle::Solid;
491
+ metrics.borderWidths.bottom = metrics.borderWidths.left = metrics.borderWidths.right = metrics.borderWidths.top =
492
+ FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor;
493
+ return metrics;
1076
494
  }
1077
495
 
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);
496
+ void ComponentView::showFocusVisual(bool show) noexcept {
497
+ if ((m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
498
+ if (show && !m_showingFocusVisual) {
499
+ m_showingFocusVisual = true;
500
+
501
+ m_focusVisual.IsVisible(true);
502
+ assert(viewProps()->enableFocusRing);
503
+ if (!m_focusInnerPrimitive) {
504
+ m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
505
+ m_focusVisual.InsertAt(m_focusInnerPrimitive->RootVisual(), 0);
1136
506
  }
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
-
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);
507
+ if (!m_focusOuterPrimitive) {
508
+ m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
509
+ m_focusVisual.InsertAt(m_focusOuterPrimitive->RootVisual(), 0);
510
+ }
511
+ updateFocusLayoutMetrics(m_layoutMetrics);
512
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
513
+ m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
514
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
515
+ m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
516
+ } else if (!show && m_showingFocusVisual) {
517
+ m_showingFocusVisual = false;
518
+ m_focusVisual.IsVisible(false);
519
+ if (m_focusInnerPrimitive) {
520
+ m_focusInnerPrimitive->markNeedsUpdate();
521
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
522
+ m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
523
+ }
524
+ if (m_focusOuterPrimitive) {
525
+ m_focusOuterPrimitive->markNeedsUpdate();
526
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
527
+ m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
1196
528
  }
1197
529
  }
1198
530
  }
1199
531
  }
1200
532
 
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
533
  void ComponentView::updateShadowProps(
1231
534
  const facebook::react::ViewProps &oldViewProps,
1232
535
  const facebook::react::ViewProps &newViewProps) noexcept {
@@ -1240,11 +543,13 @@ void ComponentView::updateShadowProps(
1240
543
 
1241
544
  void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps) noexcept {
1242
545
  auto shadow = m_compContext.CreateDropShadow();
546
+
1243
547
  shadow.Offset({viewProps.shadowOffset.width, viewProps.shadowOffset.height, 0});
1244
548
  shadow.Opacity(viewProps.shadowOpacity);
1245
549
  shadow.BlurRadius(viewProps.shadowRadius);
1246
550
  if (viewProps.shadowColor)
1247
551
  shadow.Color(theme()->Color(*viewProps.shadowColor));
552
+
1248
553
  Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
1249
554
  }
1250
555
 
@@ -1357,16 +662,16 @@ void ComponentView::Toggle() noexcept {
1357
662
  // no-op
1358
663
  }
1359
664
 
1360
- void ComponentView::updateBorderLayoutMetrics(
665
+ void ComponentView::updateClippingPath(
1361
666
  facebook::react::LayoutMetrics const &layoutMetrics,
1362
667
  const facebook::react::ViewProps &viewProps) noexcept {
1363
- auto borderMetrics = resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
668
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1364
669
 
1365
670
  if (borderMetrics.borderRadii.topLeft == 0 && borderMetrics.borderRadii.topRight == 0 &&
1366
671
  borderMetrics.borderRadii.bottomLeft == 0 && borderMetrics.borderRadii.bottomRight == 0) {
1367
672
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
1368
673
  } else {
1369
- winrt::com_ptr<ID2D1PathGeometry> pathGeometry = GenerateRoundedRectPathGeometry(
674
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
1370
675
  m_compContext,
1371
676
  borderMetrics.borderRadii,
1372
677
  {0, 0, 0, 0},
@@ -1378,24 +683,12 @@ void ComponentView::updateBorderLayoutMetrics(
1378
683
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1379
684
  pathGeometry.get());
1380
685
  }
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
686
  }
1396
687
 
1397
688
  void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
1398
- index += m_numBorderVisuals;
689
+ if (m_borderPrimitive) {
690
+ index += m_borderPrimitive->numberOfVisuals();
691
+ }
1399
692
  }
1400
693
 
1401
694
  void ComponentView::OnRenderingDeviceLost() noexcept {}
@@ -1564,6 +857,11 @@ winrt::Microsoft::ReactNative::ComponentView ViewComponentView::Create(
1564
857
  ViewComponentView::defaultProps(), compContext, tag, reactContext, ComponentViewFeatures::Default);
1565
858
  }
1566
859
 
860
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
861
+ ViewComponentView::VisualToMountChildrenInto() noexcept {
862
+ return Visual();
863
+ }
864
+
1567
865
  void ViewComponentView::MountChildComponentView(
1568
866
  const winrt::Microsoft::ReactNative::ComponentView &childComponentView,
1569
867
  uint32_t index) noexcept {
@@ -1583,7 +881,7 @@ void ViewComponentView::MountChildComponentView(
1583
881
  }
1584
882
  }
1585
883
  }
1586
- Visual().InsertAt(compositionChild->OuterVisual(), visualIndex);
884
+ VisualToMountChildrenInto().InsertAt(compositionChild->OuterVisual(), visualIndex);
1587
885
  } else {
1588
886
  m_hasNonVisualChildren = true;
1589
887
  }
@@ -1596,7 +894,7 @@ void ViewComponentView::UnmountChildComponentView(
1596
894
 
1597
895
  indexOffsetForBorder(index);
1598
896
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1599
- Visual().Remove(compositionChild->OuterVisual());
897
+ VisualToMountChildrenInto().Remove(compositionChild->OuterVisual());
1600
898
  }
1601
899
  }
1602
900
 
@@ -1898,6 +1196,9 @@ winrt::Microsoft::ReactNative::ComponentView lastDeepChild(
1898
1196
  return current;
1899
1197
  }
1900
1198
 
1199
+ // Walks the tree calling the function fn on each node.
1200
+ // If fn returns true, then walkTree stops itterating over the tree, and returns true.
1201
+ // If the tree walk completes without fn returning true, then walkTree returns false.
1901
1202
  bool walkTree(
1902
1203
  const winrt::Microsoft::ReactNative::ComponentView &view,
1903
1204
  bool forward,