@@ -41,6 +41,7 @@ constexpr float FOCUS_VISUAL_RADIUS = 3.0f;
4141// ----- m_visual <-- Background / clip - Can be a custom visual depending on Component type
4242// |
4343// ----- Border Visuals x N (BorderPrimitive attached to m_visual)
44+ // ----- Outline Visuals x N(BorderPrimitive)
4445// ----- <children> (default: directly in m_visual after border visuals)
4546// ----- m_childrenContainer (created on demand when overflow:hidden, children moved here)
4647// ------Focus Visual Container (created when hosting focus visuals)
@@ -91,6 +92,9 @@ void ComponentView::onThemeChanged() noexcept {
9192 m_borderPrimitive->onThemeChanged (
9293 m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics (m_layoutMetrics, *viewProps ()));
9394 }
95+ if (m_outlinePrimitive) {
96+ m_outlinePrimitive->onThemeChanged (outlineLayoutMetrics (), outlineBorderMetrics ());
97+ }
9498 if (m_componentHostingFocusVisual) {
9599 if (m_componentHostingFocusVisual->m_focusPrimitive ->m_focusInnerPrimitive ) {
96100 auto innerFocusMetrics = focusLayoutMetrics (true /* inner*/ );
@@ -166,6 +170,16 @@ void ComponentView::updateProps(
166170 m_borderPrimitive->updateProps (oldViewProps, newViewProps);
167171 }
168172
173+ if (m_outlinePrimitive) {
174+ if (oldViewProps.outlineOffset != newViewProps.outlineOffset ||
175+ oldViewProps.outlineWidth != newViewProps.outlineWidth ||
176+ oldViewProps.borderRadii != newViewProps.borderRadii ||
177+ oldViewProps.outlineColor != newViewProps.outlineColor ||
178+ oldViewProps.outlineStyle != newViewProps.outlineStyle ) {
179+ m_outlinePrimitive->markNeedsUpdate ();
180+ }
181+ }
182+
169183 if (m_componentHostingFocusVisual) {
170184 if (!newViewProps.enableFocusRing ) {
171185 m_componentHostingFocusVisual->hostFocusVisual (false , get_strong ());
@@ -218,6 +232,9 @@ void ComponentView::updateLayoutMetrics(
218232 if (m_borderPrimitive) {
219233 m_borderPrimitive->markNeedsUpdate ();
220234 }
235+ if (m_outlinePrimitive) {
236+ m_outlinePrimitive->markNeedsUpdate ();
237+ }
221238
222239 if (m_componentHostingFocusVisual) {
223240 m_componentHostingFocusVisual->updateFocusLayoutMetrics ();
@@ -301,6 +318,21 @@ void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentView
301318 if (m_borderPrimitive) {
302319 m_borderPrimitive->finalize (m_layoutMetrics, borderMetrics);
303320 }
321+
322+ auto outlineMetrics = outlineBorderMetrics ();
323+ if (!m_outlinePrimitive && BorderPrimitive::requiresBorder (outlineMetrics, theme ())) {
324+ m_outlinePrimitive = std::make_shared<BorderPrimitive>(*this );
325+ Visual ().InsertAt (m_outlinePrimitive->RootVisual (), m_borderPrimitive ? m_borderPrimitive->numberOfVisuals () : 0 );
326+ }
327+
328+ if (m_outlinePrimitive) {
329+ auto offset = pixelRoundAndScaleBorderWidth (viewProps ()->outlineWidth , m_layoutMetrics.pointScaleFactor ) +
330+ std::round (viewProps ()->outlineOffset * m_layoutMetrics.pointScaleFactor );
331+ m_outlinePrimitive->RootVisual ().Offset ({-offset, -offset, 0 .0f });
332+ m_outlinePrimitive->RootVisual ().RelativeSizeWithOffset ({offset * 2 , offset * 2 }, {1 .0f , 1 .0f });
333+
334+ m_outlinePrimitive->finalize (outlineLayoutMetrics (), outlineMetrics);
335+ }
304336 }
305337
306338 if (m_componentHostingFocusVisual) {
@@ -581,6 +613,67 @@ facebook::react::LayoutMetrics ComponentView::focusLayoutMetrics(bool inner) con
581613 return layoutMetrics;
582614}
583615
616+ facebook::react::LayoutMetrics ComponentView::outlineLayoutMetrics () const noexcept {
617+ auto &props = *viewProps ();
618+ auto offset = (pixelRoundAndScaleBorderWidth (viewProps ()->outlineWidth , m_layoutMetrics.pointScaleFactor ) +
619+ std::round (viewProps ()->outlineOffset * m_layoutMetrics.pointScaleFactor )) /
620+ m_layoutMetrics.pointScaleFactor ;
621+ facebook::react::LayoutMetrics layoutMetrics = m_layoutMetrics;
622+ layoutMetrics.frame .origin .x -= offset;
623+ layoutMetrics.frame .origin .y -= offset;
624+ layoutMetrics.frame .size .height += offset * 2 ;
625+ layoutMetrics.frame .size .width += offset * 2 ;
626+ return layoutMetrics;
627+ }
628+
629+ facebook::react::BorderMetrics ComponentView::outlineBorderMetrics () const noexcept {
630+ auto &props = *viewProps ();
631+
632+ facebook::react::BorderMetrics metrics = BorderPrimitive::resolveAndAlignBorderMetrics (m_layoutMetrics, props);
633+ metrics.borderColors .bottom = metrics.borderColors .left = metrics.borderColors .right = metrics.borderColors .top =
634+ props.outlineColor ;
635+
636+ auto offset = pixelRoundAndScaleBorderWidth (viewProps ()->outlineWidth , m_layoutMetrics.pointScaleFactor ) +
637+ std::round (viewProps ()->outlineOffset * m_layoutMetrics.pointScaleFactor );
638+
639+ if (metrics.borderRadii .bottomLeft .horizontal )
640+ metrics.borderRadii .bottomLeft .horizontal = std::max (0 .0f , metrics.borderRadii .bottomLeft .horizontal + offset);
641+ if (metrics.borderRadii .bottomLeft .vertical )
642+ metrics.borderRadii .bottomLeft .vertical = std::max (0 .0f , metrics.borderRadii .bottomLeft .vertical + offset);
643+ if (metrics.borderRadii .bottomRight .horizontal )
644+ metrics.borderRadii .bottomRight .horizontal = std::max (0 .0f , metrics.borderRadii .bottomRight .horizontal + offset);
645+ if (metrics.borderRadii .bottomRight .vertical )
646+ metrics.borderRadii .bottomRight .vertical = std::max (0 .0f , metrics.borderRadii .bottomRight .vertical + offset);
647+ if (metrics.borderRadii .topLeft .horizontal )
648+ metrics.borderRadii .topLeft .horizontal = std::max (0 .0f , metrics.borderRadii .topLeft .horizontal + offset);
649+ if (metrics.borderRadii .topLeft .vertical )
650+ metrics.borderRadii .topLeft .vertical = std::max (0 .0f , metrics.borderRadii .topLeft .vertical + offset);
651+ if (metrics.borderRadii .topRight .horizontal )
652+ metrics.borderRadii .topRight .horizontal = std::max (0 .0f , metrics.borderRadii .topRight .horizontal + offset);
653+ if (metrics.borderRadii .topRight .vertical )
654+ metrics.borderRadii .topRight .vertical = std::max (0 .0f , metrics.borderRadii .topRight .vertical + offset);
655+
656+ static_assert (
657+ facebook::react::BorderStyle::Solid ==
658+ static_cast <facebook::react::BorderStyle>(facebook::react::OutlineStyle::Solid));
659+ static_assert (
660+ facebook::react::BorderStyle::Dotted ==
661+ static_cast <facebook::react::BorderStyle>(facebook::react::OutlineStyle::Dotted));
662+ static_assert (
663+ facebook::react::BorderStyle::Dashed ==
664+ static_cast <facebook::react::BorderStyle>(facebook::react::OutlineStyle::Dashed));
665+ assert (
666+ props.outlineStyle == facebook::react::OutlineStyle::Solid ||
667+ props.outlineStyle == facebook::react::OutlineStyle::Dotted ||
668+ props.outlineStyle == facebook::react::OutlineStyle::Dashed);
669+ metrics.borderStyles .bottom = metrics.borderStyles .left = metrics.borderStyles .right = metrics.borderStyles .top =
670+ static_cast <facebook::react::BorderStyle>(props.outlineStyle );
671+
672+ metrics.borderWidths .bottom = metrics.borderWidths .left = metrics.borderWidths .right = metrics.borderWidths .top =
673+ pixelRoundAndScaleBorderWidth (viewProps ()->outlineWidth , m_layoutMetrics.pointScaleFactor );
674+ return metrics;
675+ }
676+
584677facebook::react::BorderMetrics ComponentView::focusBorderMetrics (
585678 bool inner,
586679 const facebook::react::LayoutMetrics &layoutMetrics) const noexcept {
@@ -934,6 +1027,9 @@ void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
9341027 if (m_borderPrimitive) {
9351028 index += m_borderPrimitive->numberOfVisuals ();
9361029 }
1030+ if (m_outlinePrimitive) {
1031+ index += 1 ;
1032+ }
9371033}
9381034
9391035void ComponentView::OnRenderingDeviceLost () noexcept {}
0 commit comments