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] Expand native theming implementation #12287

Merged
merged 13 commits into from
Oct 25, 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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"clang-format.language.javascript.enable": false,
"clang-format.language.typescript.enable": false,
"clang-format.assumeFilename": "${workspaceFolder}/.clang-format",
"clang-format.executable": "${workspaceRoot}/vnext/node_modules/.bin/clang-format",
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric] Native Theming",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion packages/e2e-test-app-fabric/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.exports = {
}\\RNTesterApp-Fabric.exe`,
appWorkingDir: 'windows\\RNTesterApp-Fabric',
enableAutomationChannel: true,
/* -- Enable for more detailed logging
/* // Enable for more detailed logging
webdriverOptions: {
// Level of logging verbosity: trace | debug | info | warn | error
logLevel: 'info',
Expand Down
24 changes: 21 additions & 3 deletions packages/e2e-test-app-fabric/test/RNTesterNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,29 @@ export async function goToApiExample(example: string) {
}

async function goToExample(example: string) {
const searchString = regexEscape(
example.substring(0, Math.min(example.length, 8)),
);

// Filter the list down to the one test, to improve the stability of selectors
const searchBox = await app.findElementByTestID('explorer_search');
await searchBox.addValue(['Backspace', 'Backspace', 'Backspace']);
// Only grab first three characters of string to reduce cases in WebDriverIO mistyping.
await searchBox.addValue(regexEscape(example.substring(0, 3)));

await app.waitUntil(
async () => {
await searchBox.setValue(searchString);
return (await searchBox.getText()) === searchString;
},
{
interval: 1500,
timeout: 5000,
timeoutMsg: `Unable to enter correct search text into test searchbox.`,
},
);

// We cannot just click on exampleButton, since it it is likely off screen.
// So we first search for the item hopfully causing the item to be one of the few remaining in the list - and therefore onscreen
// Ideally we'd either use UIA to invoke the specific item, or ensure that the item is within view
// Once we have those UIA patterns implemented we should update this logic.
const exampleButton = await app.findElementByTestID(example);
await exampleButton.waitForDisplayed({timeout: 5000});
await exampleButton.click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,13 @@ struct WindowData {
viewOptions.ComponentName(appName);
auto windowData = WindowData::GetFromWindow(hwnd);

if (!m_compRootView) {
if (windowData->m_useLiftedComposition) {
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView(g_liftedCompositor);
} else {
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView();
}
if (m_compRootView)
break;

if (windowData->m_useLiftedComposition) {
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView(g_liftedCompositor);
} else {
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView();
}

m_compRootView.ReactViewHost(
Expand Down
6 changes: 3 additions & 3 deletions vnext/Microsoft.ReactNative/CompositionSwitcher.idl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ namespace Microsoft.ReactNative.Composition
[experimental]
interface IActivityVisual requires IVisual
{
void Color(Windows.UI.Color color);
void Brush(IBrush brush);
}

[webhosthidden]
Expand All @@ -107,7 +107,7 @@ namespace Microsoft.ReactNative.Composition
void Size(Windows.Foundation.Numerics.Vector2 size);
void Position(Windows.Foundation.Numerics.Vector2 position);
Boolean IsVisible { get; set; };
void Color(Windows.UI.Color color);
void Brush(IBrush brush);
}

[webhosthidden]
Expand All @@ -118,7 +118,7 @@ namespace Microsoft.ReactNative.Composition
void Size(Windows.Foundation.Numerics.Vector2 size);
void Position(Windows.Foundation.Numerics.Vector2 position);
Boolean IsVisible { get; set; };
void Color(Windows.UI.Color color);
void Brush(IBrush brush);
}

[webhosthidden]
Expand Down
17 changes: 17 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/ComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/LayoutMetrics.h>

#include <Fabric/Composition/Theme.h>
#include <winrt/Microsoft.ReactNative.Composition.Input.h>

namespace Microsoft::ReactNative {
Expand All @@ -25,6 +26,12 @@ enum class RNComponentViewUpdateMask : std::uint_fast8_t {
All = Props | EventEmitter | State | LayoutMetrics
};

enum class ClipState : std::uint_fast8_t {
NoClip = 0,
PartialClip = 1,
FullyClipped = 2,
};

DEFINE_ENUM_FLAG_OPERATORS(RNComponentViewUpdateMask);

struct RootComponentView;
Expand Down Expand Up @@ -62,11 +69,16 @@ struct IComponentView {
virtual RootComponentView *rootComponentView() noexcept = 0;
virtual void parent(IComponentView *parent) noexcept = 0;
virtual IComponentView *parent() const noexcept = 0;
virtual void theme(const std::shared_ptr<Composition::Theme> &theme) noexcept = 0;
virtual std::shared_ptr<Composition::Theme> &theme() const noexcept = 0;
virtual void onThemeChanged() noexcept = 0;
virtual const std::vector<IComponentView *> &children() const noexcept = 0;
// Run fn on all children of this node until fn returns true
// returns true if the fn ever returned true
virtual bool runOnChildren(bool forward, Mso::Functor<bool(IComponentView &)> &fn) noexcept = 0;
virtual RECT getClientRect() const noexcept = 0;
// The offset from this elements parent to its children (accounts for things like scroll position)
virtual facebook::react::Point getClientOffset() const noexcept = 0;
virtual void onFocusLost() noexcept = 0;
virtual void onFocusGained() noexcept = 0;
virtual void onPointerEntered(
Expand Down Expand Up @@ -101,8 +113,13 @@ struct IComponentView {
facebook::react::Point &localPt,
bool ignorePointerEvents = false) const noexcept = 0;
virtual winrt::IInspectable EnsureUiaProvider() noexcept = 0;
virtual std::optional<std::string> getAcccessiblityValue() noexcept = 0;
virtual void setAcccessiblityValue(std::string &&value) noexcept = 0;
virtual bool getAcccessiblityIsReadOnly() noexcept = 0;

// Notify up the tree to bring the rect into view by scrolling as needed
virtual void StartBringIntoView(BringIntoViewOptions &&args) noexcept = 0;
virtual ClipState getClipState() noexcept = 0;
};

// Run fn on all nodes of the component view tree starting from this one until fn returns true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,26 @@

#include <Fabric/DWriteHelpers.h>
#include "CompositionDynamicAutomationProvider.h"
#include "RootComponentView.h"
#include "Unicode.h"

namespace Microsoft::ReactNative {

AbiCompositionViewComponentView::AbiCompositionViewComponentView(
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder)
: Super(compContext, tag), m_builder(builder) {
: Super(compContext, tag, reactContext, CompositionComponentViewFeatures::Default), m_builder(builder) {
static auto const defaultProps = std::make_shared<AbiViewProps const>();
m_props = defaultProps;
m_handle = Builder().CreateView(reactContext, compContext);
m_handle = Builder().CreateView(reactContext.Handle(), compContext);
m_visual = Builder().CreateVisual(m_handle);
OuterVisual().InsertAt(m_visual, 0);
}

std::shared_ptr<AbiCompositionViewComponentView> AbiCompositionViewComponentView::Create(
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept {
Expand Down Expand Up @@ -60,7 +61,7 @@ void AbiCompositionViewComponentView::updateProps(
updateAccessibilityProps(oldViewProps, newViewProps);
// updateShadowProps(oldViewProps, newViewProps, m_visual);
// updateTransformProps(oldViewProps, newViewProps, m_visual);
updateBorderProps(oldViewProps, newViewProps);
Super::updateProps(props, oldProps);

Builder().UpdateProps(m_handle, newViewProps.UserProps());

Expand All @@ -74,7 +75,7 @@ void AbiCompositionViewComponentView::updateLayoutMetrics(
OuterVisual().IsVisible(layoutMetrics.displayType != facebook::react::DisplayType::None);
}

updateBorderLayoutMetrics(layoutMetrics, *m_props);
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);

winrt::Microsoft::ReactNative::Composition::LayoutMetrics lm;
Builder().UpdateLayoutMetrics(
Expand All @@ -84,21 +85,15 @@ void AbiCompositionViewComponentView::updateLayoutMetrics(
layoutMetrics.frame.size.width,
layoutMetrics.frame.size.height},
layoutMetrics.pointScaleFactor});

m_layoutMetrics = layoutMetrics;
}

void AbiCompositionViewComponentView::updateState(
facebook::react::State::Shared const &state,
facebook::react::State::Shared const &oldState) noexcept {}

void AbiCompositionViewComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {
Super::finalizeUpdates(updateMask);
Builder().FinalizeUpdates(m_handle);

if (m_needsBorderUpdate) {
m_needsBorderUpdate = false;
UpdateSpecialBorderLayers(m_layoutMetrics, *m_props);
}
}

bool AbiCompositionViewComponentView::focusable() const noexcept {
Expand Down Expand Up @@ -168,7 +163,8 @@ AbiCompositionViewComponentView::supplementalComponentDescriptorProviders() noex
}

void AbiCompositionViewComponentView::prepareForRecycle() noexcept {}
facebook::react::Props::Shared AbiCompositionViewComponentView::props() noexcept {

facebook::react::SharedViewProps AbiCompositionViewComponentView::viewProps() noexcept {
return m_props;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct AbiCompositionViewComponentView : CompositionBaseComponentView {
using Super = CompositionBaseComponentView;

[[nodiscard]] static std::shared_ptr<AbiCompositionViewComponentView> Create(
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept;
Expand Down Expand Up @@ -64,14 +64,14 @@ struct AbiCompositionViewComponentView : CompositionBaseComponentView {
const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept override;
std::vector<facebook::react::ComponentDescriptorProvider> supplementalComponentDescriptorProviders() noexcept
override;
facebook::react::Props::Shared props() noexcept override;
facebook::react::SharedViewProps viewProps() noexcept override;
facebook::react::Tag hitTest(facebook::react::Point pt, facebook::react::Point &localPt, bool ignorePointerEvents)
const noexcept override;
winrt::Microsoft::ReactNative::Composition::IVisual Visual() const noexcept override;

private:
AbiCompositionViewComponentView(
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <Windows.UI.Composition.h>
#include <Windows.h>
#include "CompositionContextHelper.h"
#include "RootComponentView.h"

namespace Microsoft::ReactNative {

Expand All @@ -24,7 +25,7 @@ ActivityIndicatorComponentView::ActivityIndicatorComponentView(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::ReactContext const &reactContext)
: Super(compContext, tag), m_context(reactContext) {
: Super(compContext, tag, reactContext, CompositionComponentViewFeatures::Default) {
m_props = std::make_shared<facebook::react::ActivityIndicatorViewProps const>();
}

Expand All @@ -44,6 +45,14 @@ void ActivityIndicatorComponentView::handleCommand(std::string const &commandNam
Super::handleCommand(commandName, arg);
}

void ActivityIndicatorComponentView::updateProgressColor(const facebook::react::SharedColor &color) noexcept {
if (color) {
m_ActivityIndicatorVisual.Brush(theme()->Brush(*color));
} else {
m_ActivityIndicatorVisual.Brush(theme()->PlatformBrush("ProgressRingForegroundTheme"));
}
}

void ActivityIndicatorComponentView::updateProps(
facebook::react::Props::Shared const &props,
facebook::react::Props::Shared const &oldProps) noexcept {
Expand All @@ -53,15 +62,16 @@ void ActivityIndicatorComponentView::updateProps(
ensureVisual();

// update color if needed
if (newViewProps->color && (!oldProps || newViewProps->color != oldViewProps->color)) {
m_ActivityIndicatorVisual.Color(newViewProps->color.AsWindowsColor());
if (!oldProps || newViewProps->color != oldViewProps->color) {
updateProgressColor(newViewProps->color);
}

if (newViewProps->animating != oldViewProps->animating) {
m_ActivityIndicatorVisual.IsVisible(newViewProps->animating);
}

updateBorderProps(*oldViewProps, *newViewProps);
Super::updateProps(props, oldProps);

m_props = std::static_pointer_cast<facebook::react::ViewProps const>(props);
}

Expand All @@ -79,20 +89,15 @@ void ActivityIndicatorComponentView::updateLayoutMetrics(
OuterVisual().IsVisible(layoutMetrics.displayType != facebook::react::DisplayType::None);
}

updateBorderLayoutMetrics(layoutMetrics, *m_props);
m_layoutMetrics = layoutMetrics;

UpdateCenterPropertySet();
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
m_visual.Size(
{layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
}

void ActivityIndicatorComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {}

void ActivityIndicatorComponentView::prepareForRecycle() noexcept {}

facebook::react::Props::Shared ActivityIndicatorComponentView::props() noexcept {
facebook::react::SharedViewProps ActivityIndicatorComponentView::viewProps() noexcept {
return m_props;
}

Expand Down Expand Up @@ -126,6 +131,10 @@ winrt::Microsoft::ReactNative::Composition::IVisual ActivityIndicatorComponentVi
return m_visual;
}

void ActivityIndicatorComponentView::onThemeChanged() noexcept {
updateProgressColor(std::static_pointer_cast<const facebook::react::ActivityIndicatorViewProps>(m_props)->color);
}

bool ActivityIndicatorComponentView::focusable() const noexcept {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ struct ActivityIndicatorComponentView : CompositionBaseComponentView {
void updateLayoutMetrics(
facebook::react::LayoutMetrics const &layoutMetrics,
facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept override;
void finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept override;
void prepareForRecycle() noexcept override;
facebook::react::Props::Shared props() noexcept override;
facebook::react::SharedViewProps viewProps() noexcept override;
bool focusable() const noexcept override;
void onThemeChanged() noexcept override;

facebook::react::Tag hitTest(facebook::react::Point pt, facebook::react::Point &localPt, bool ignorePointerEvents)
const noexcept override;
Expand All @@ -49,10 +49,10 @@ struct ActivityIndicatorComponentView : CompositionBaseComponentView {
winrt::Microsoft::ReactNative::ReactContext const &reactContext);

void ensureVisual() noexcept;
void updateProgressColor(const facebook::react::SharedColor &color) noexcept;

winrt::Microsoft::ReactNative::Composition::ISpriteVisual m_visual{nullptr};
winrt::Microsoft::ReactNative::Composition::IActivityVisual m_ActivityIndicatorVisual{nullptr};
winrt::Microsoft::ReactNative::ReactContext m_context;
facebook::react::SharedViewProps m_props;
};

Expand Down
Loading