Skip to content

Commit

Permalink
[Fabric] add support for enableFocusRing (#11323)
Browse files Browse the repository at this point in the history
* [Fabric] add support for enableFocusRing

* Change files

* format

* fix

* fixes
  • Loading branch information
acoates-ms committed Mar 7, 2023
1 parent 7bcdfb0 commit 3d99dfe
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] add support for enableFocusRing",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
10 changes: 10 additions & 0 deletions vnext/Microsoft.ReactNative/CompositionSwitcher.idl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ namespace Microsoft.ReactNative.Composition
Boolean IsVisible { get; set; };
}

[webhosthidden]
[experimental]
interface IFocusVisual
{
IVisual InnerVisual { get; };
Boolean IsFocused { get; set; };
Single ScaleFactor { get; set; };
}

[webhosthidden]
[experimental]
interface ICompositionContext
Expand All @@ -112,6 +121,7 @@ namespace Microsoft.ReactNative.Composition
SpriteVisual CreateSpriteVisual();
ScrollVisual CreateScrollerVisual();
ICaretVisual CreateCaretVisual();
IFocusVisual CreateFocusVisual();
IDropShadow CreateDropShadow();
IBrush CreateColorBrush(Windows.UI.Color color);
SurfaceBrush CreateSurfaceBrush(ICompositionDrawingSurface surface);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ struct CompVisual : public winrt::implements<
}

void SetClippingPath(ID2D1Geometry *clippingPath) noexcept {
if (!clippingPath) {
m_visual.Clip(nullptr);
return;
}
auto geometry = winrt::make<GeometrySource>(clippingPath);
auto path = winrt::Windows::UI::Composition::CompositionPath(geometry);
auto pathgeo = m_visual.Compositor().CreatePathGeometry(path);
Expand Down Expand Up @@ -512,6 +516,10 @@ struct CompScrollerVisual : winrt::Microsoft::ReactNative::Composition::implemen
}

void SetClippingPath(ID2D1Geometry *clippingPath) noexcept {
if (!clippingPath) {
m_visual.Clip(nullptr);
return;
}
auto geometry = winrt::make<GeometrySource>(clippingPath);
auto path = winrt::Windows::UI::Composition::CompositionPath(geometry);
auto pathgeo = m_visual.Compositor().CreatePathGeometry(path);
Expand Down Expand Up @@ -628,6 +636,54 @@ struct CompCaretVisual : winrt::implements<CompCaretVisual, winrt::Microsoft::Re
winrt::Windows::UI::Composition::Compositor m_compositor{nullptr};
};

struct CompFocusVisual : winrt::implements<CompFocusVisual, winrt::Microsoft::ReactNative::Composition::IFocusVisual> {
CompFocusVisual(winrt::Windows::UI::Composition::Compositor const &compositor)
: m_compVisual(compositor.CreateSpriteVisual()), m_brush(compositor.CreateNineGridBrush()) {
m_visual = winrt::make<Composition::CompSpriteVisual>(m_compVisual);

m_compVisual.Opacity(1.0f);
m_compVisual.RelativeSizeAdjustment({1, 1});

m_brush.Source(compositor.CreateColorBrush(winrt::Windows::UI::Colors::Black()));
m_brush.IsCenterHollow(true);
}

winrt::Microsoft::ReactNative::Composition::IVisual InnerVisual() const noexcept {
return m_visual;
}

bool IsFocused() const noexcept {
return m_compVisual.Brush() != nullptr;
}

void IsFocused(bool value) noexcept {
if (value) {
m_compVisual.Brush(m_brush);
} else {
m_compVisual.Brush(nullptr);
}
}

float ScaleFactor() const noexcept {
return m_scaleFactor;
}

void ScaleFactor(float scaleFactor) noexcept {
if (m_scaleFactor == scaleFactor) {
return;
}
m_scaleFactor = scaleFactor;
auto inset = 2 * scaleFactor;
m_brush.SetInsets(inset, inset, inset, inset);
}

private:
float m_scaleFactor{0};
const winrt::Windows::UI::Composition::CompositionNineGridBrush m_brush;
const winrt::Windows::UI::Composition::SpriteVisual m_compVisual;
winrt::Microsoft::ReactNative::Composition::IVisual m_visual{nullptr};
};

struct CompContext : winrt::implements<
CompContext,
winrt::Microsoft::ReactNative::Composition::ICompositionContext,
Expand Down Expand Up @@ -736,6 +792,10 @@ struct CompContext : winrt::implements<
return winrt::make<Composition::CompCaretVisual>(m_compositor);
}

winrt::Microsoft::ReactNative::Composition::IFocusVisual CreateFocusVisual() noexcept {
return winrt::make<Composition::CompFocusVisual>(m_compositor);
}

winrt::Windows::UI::Composition::CompositionGraphicsDevice CompositionGraphicsDevice() noexcept {
if (!m_compositionGraphicsDevice) {
// To create a composition graphics device, we need to QI for another interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ namespace Microsoft::ReactNative {
CompositionBaseComponentView::CompositionBaseComponentView(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag)
: m_tag(tag), m_compContext(compContext) {}
: m_tag(tag), m_compContext(compContext) {
m_outerVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
// CreateContainerVisual in ICompositionContext
m_focusVisual = compContext.CreateFocusVisual();
m_outerVisual.InsertAt(m_focusVisual.InnerVisual(), 0);
}

facebook::react::Tag CompositionBaseComponentView::tag() const noexcept {
return m_tag;
Expand Down Expand Up @@ -71,10 +76,14 @@ bool CompositionBaseComponentView::runOnChildren(bool forward, Mso::Functor<bool

void CompositionBaseComponentView::onFocusLost() noexcept {
m_eventEmitter->onBlur();
showFocusVisual(false);
}

void CompositionBaseComponentView::onFocusGained() noexcept {
m_eventEmitter->onFocus();
if (m_enableFocusVisual) {
showFocusVisual(true);
}
}

void CompositionBaseComponentView::updateEventEmitter(
Expand All @@ -83,6 +92,18 @@ void CompositionBaseComponentView::updateEventEmitter(
}

void CompositionBaseComponentView::handleCommand(std::string const &commandName, folly::dynamic const &arg) noexcept {
if (commandName == "focus") {
if (auto root = rootComponentView()) {
root->SetFocusedComponent(this);
}
return;
}
if (commandName == "blur") {
if (auto root = rootComponentView()) {
root->SetFocusedComponent(nullptr); // Todo store this component as previously focused element
}
return;
}
assert(false); // Unhandled command
}

Expand Down Expand Up @@ -980,6 +1001,19 @@ void CompositionBaseComponentView::UpdateSpecialBorderLayers(
}
}

winrt::Microsoft::ReactNative::Composition::IVisual CompositionBaseComponentView::OuterVisual() const noexcept {
return m_outerVisual ? m_outerVisual : Visual();
}

void CompositionBaseComponentView::showFocusVisual(bool show) noexcept {
if (show) {
assert(m_enableFocusVisual);
m_focusVisual.IsFocused(true);
} else {
m_focusVisual.IsFocused(false);
}
}

void CompositionBaseComponentView::updateBorderProps(
const facebook::react::ViewProps &oldViewProps,
const facebook::react::ViewProps &newViewProps) noexcept {
Expand All @@ -988,6 +1022,11 @@ void CompositionBaseComponentView::updateBorderProps(
oldViewProps.borderStyles != newViewProps.borderStyles) {
m_needsBorderUpdate = true;
}

m_enableFocusVisual = newViewProps.enableFocusRing;
if (!m_enableFocusVisual) {
showFocusVisual(false);
}
}

void CompositionBaseComponentView::updateBorderLayoutMetrics(
Expand All @@ -1014,6 +1053,16 @@ void CompositionBaseComponentView::updateBorderLayoutMetrics(
if (m_layoutMetrics != layoutMetrics) {
m_needsBorderUpdate = true;
}

m_focusVisual.ScaleFactor(layoutMetrics.pointScaleFactor);
OuterVisual().Size(
{layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
OuterVisual().Offset({
layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
0.0f,
});
}

void CompositionBaseComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
Expand Down Expand Up @@ -1077,7 +1126,7 @@ void CompositionBaseComponentView::EnsureTransformMatrixFacade() noexcept {
.CreateExpressionAnimation(
L"Matrix4x4.CreateFromScale(PS.dpiScale3Inv) * Matrix4x4.CreateFromTranslation(PS.translation) * PS.transform * Matrix4x4.CreateFromScale(PS.dpiScale3)");
expression.SetReferenceParameter(L"PS", centerPointPropSet);
winrt::Microsoft::ReactNative::Composition::implementation::CompositionContextHelper::InnerVisual(Visual())
winrt::Microsoft::ReactNative::Composition::implementation::CompositionContextHelper::InnerVisual(OuterVisual())
.StartAnimation(L"TransformMatrix", expression);
}

Expand All @@ -1101,6 +1150,7 @@ CompositionViewComponentView::CompositionViewComponentView(
static auto const defaultProps = std::make_shared<facebook::react::ViewProps const>();
m_props = defaultProps;
m_visual = m_compContext.CreateSpriteVisual();
OuterVisual().InsertAt(m_visual, 0);
}

std::vector<facebook::react::ComponentDescriptorProvider>
Expand All @@ -1117,7 +1167,7 @@ void CompositionViewComponentView::mountChildComponentView(

childComponentView.parent(this);

m_visual.InsertAt(static_cast<CompositionBaseComponentView &>(childComponentView).Visual(), index);
m_visual.InsertAt(static_cast<CompositionBaseComponentView &>(childComponentView).OuterVisual(), index);
}

void CompositionViewComponentView::unmountChildComponentView(
Expand All @@ -1128,7 +1178,7 @@ void CompositionViewComponentView::unmountChildComponentView(
indexOffsetForBorder(index);

childComponentView.parent(nullptr);
m_visual.Remove(static_cast<CompositionBaseComponentView &>(childComponentView).Visual());
m_visual.Remove(static_cast<CompositionBaseComponentView &>(childComponentView).OuterVisual());
}

void CompositionViewComponentView::updateProps(
Expand Down Expand Up @@ -1240,7 +1290,7 @@ void CompositionViewComponentView::updateLayoutMetrics(
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept {
// Set Position & Size Properties
if ((layoutMetrics.displayType != m_layoutMetrics.displayType)) {
m_visual.IsVisible(layoutMetrics.displayType != facebook::react::DisplayType::None);
OuterVisual().IsVisible(layoutMetrics.displayType != facebook::react::DisplayType::None);
}

updateBorderLayoutMetrics(layoutMetrics, *m_props);
Expand All @@ -1251,11 +1301,6 @@ void CompositionViewComponentView::updateLayoutMetrics(
m_visual.Size(
{layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
m_visual.Offset({
layoutMetrics.frame.origin.x * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.origin.y * layoutMetrics.pointScaleFactor,
0.0f,
});
}

void CompositionViewComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {
Expand All @@ -1278,6 +1323,19 @@ bool CompositionViewComponentView::focusable() const noexcept {
return m_props->focusable;
}

IComponentView *lastDeepChild(IComponentView &view) noexcept {
auto current = &view;
while (current) {
auto children = current->children();
auto itLastChild = children.rbegin();
if (itLastChild == children.rend()) {
break;
}
current = *itLastChild;
}
return current;
}

bool walkTree(IComponentView &view, bool forward, Mso::Functor<bool(IComponentView &)> &fn) noexcept {
if (forward) {
if (fn(view)) {
Expand All @@ -1289,40 +1347,41 @@ bool walkTree(IComponentView &view, bool forward, Mso::Functor<bool(IComponentVi
return true;
}

auto parent = view.parent();
if (parent) {
auto current = &view;
auto parent = current->parent();
while (parent) {
auto &parentsChildren = parent->children();
auto itNextView = std::find(parentsChildren.begin(), parentsChildren.end(), &view);
auto itNextView = std::find(parentsChildren.begin(), parentsChildren.end(), current);
assert(itNextView != parentsChildren.end());
auto index = std::distance(parentsChildren.begin(), itNextView);
for (auto it = parentsChildren.begin() + index + 1; it != parentsChildren.end(); ++it) {
if (walkTree(**it, true, fn))
return true;
++itNextView;
if (itNextView != parentsChildren.end()) {
return walkTree(**itNextView, true, fn);
}
current = parent;
parent = current->parent();
}

} else {
auto parent = view.parent();
if (parent) {
auto current = &view;
auto parent = current->parent();
while (parent) {
auto &parentsChildren = parent->children();
auto itNextView = std::find(parentsChildren.rbegin(), parentsChildren.rend(), &view);
auto itNextView = std::find(parentsChildren.rbegin(), parentsChildren.rend(), current);
assert(itNextView != parentsChildren.rend());
auto index = std::distance(parentsChildren.rbegin(), itNextView);
for (auto it = parentsChildren.rbegin() + index + 1; it != parentsChildren.rend(); ++it) {
if (fn(**it))
return true;
if (walkTree(**it, false, fn))
++itNextView;
if (itNextView != parentsChildren.rend()) {
auto lastChild = lastDeepChild(**itNextView);
if (fn(*lastChild))
return true;
return walkTree(*lastChild, false, fn);
}
}

for (auto it = view.children().rbegin(); it != view.children().rend(); ++it) {
if (fn(**it))
if (fn(*parent)) {
return true;
}

if (fn(view)) {
return true;
}
current = parent;
parent = current->parent();
}
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ struct CompositionBaseComponentView : public IComponentView {
facebook::react::Tag tag);

virtual winrt::Microsoft::ReactNative::Composition::IVisual Visual() const noexcept = 0;
// Visual that should be parented to this ComponentView's parent
virtual winrt::Microsoft::ReactNative::Composition::IVisual OuterVisual() const noexcept;
void updateEventEmitter(facebook::react::EventEmitter::Shared const &eventEmitter) noexcept override;
const facebook::react::SharedViewEventEmitter &GetEventEmitter() const noexcept;
void handleCommand(std::string const &commandName, folly::dynamic const &arg) noexcept override;
Expand Down Expand Up @@ -75,7 +77,13 @@ struct CompositionBaseComponentView : public IComponentView {
facebook::react::LayoutMetrics m_layoutMetrics;
bool m_needsBorderUpdate{false};
bool m_hasTransformMatrixFacade{false};
bool m_enableFocusVisual{false};
uint8_t m_numBorderVisuals{0};

private:
void showFocusVisual(bool show) noexcept;
winrt::Microsoft::ReactNative::Composition::IFocusVisual m_focusVisual{nullptr};
winrt::Microsoft::ReactNative::Composition::IVisual m_outerVisual{nullptr};
};

struct CompositionViewComponentView : public CompositionBaseComponentView {
Expand Down
Loading

0 comments on commit 3d99dfe

Please sign in to comment.