Skip to content

Commit

Permalink
[Fabric] Expand native theming implementation (#12287)
Browse files Browse the repository at this point in the history
* Start of theming

* more fix

* More theming

* handle refresh

* fix

* format

* Change files

* fix

* Fix issue where items outside of the current viewport of a scrollview can get hittest

* Fix tabbing

* fix

* revert lock file changes
  • Loading branch information
acoates-ms authored Oct 25, 2023
1 parent 0007fe0 commit 869f2c4
Show file tree
Hide file tree
Showing 39 changed files with 1,093 additions and 295 deletions.
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

0 comments on commit 869f2c4

Please sign in to comment.