Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fabric] add support for enableFocusRing #11323

Merged
merged 5 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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