Skip to content

Commit 869f2c4

Browse files
authored
[Fabric] Expand native theming implementation (#12287)
* 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
1 parent 0007fe0 commit 869f2c4

39 files changed

+1093
-295
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"clang-format.language.javascript.enable": false,
6060
"clang-format.language.typescript.enable": false,
6161
"clang-format.assumeFilename": "${workspaceFolder}/.clang-format",
62-
"clang-format.executable": "${workspaceRoot}/vnext/node_modules/.bin/clang-format",
62+
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format",
6363
"typescript.tsdk": "node_modules\\typescript\\lib",
6464
"editor.codeActionsOnSave": {
6565
"source.fixAll.eslint": true
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "[Fabric] Native Theming",
4+
"packageName": "react-native-windows",
5+
"email": "30809111+acoates-ms@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/e2e-test-app-fabric/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ module.exports = {
6868
}\\RNTesterApp-Fabric.exe`,
6969
appWorkingDir: 'windows\\RNTesterApp-Fabric',
7070
enableAutomationChannel: true,
71-
/* -- Enable for more detailed logging
71+
/* // Enable for more detailed logging
7272
webdriverOptions: {
7373
// Level of logging verbosity: trace | debug | info | warn | error
7474
logLevel: 'info',

packages/e2e-test-app-fabric/test/RNTesterNavigation.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,29 @@ export async function goToApiExample(example: string) {
2828
}
2929

3030
async function goToExample(example: string) {
31+
const searchString = regexEscape(
32+
example.substring(0, Math.min(example.length, 8)),
33+
);
34+
3135
// Filter the list down to the one test, to improve the stability of selectors
3236
const searchBox = await app.findElementByTestID('explorer_search');
33-
await searchBox.addValue(['Backspace', 'Backspace', 'Backspace']);
34-
// Only grab first three characters of string to reduce cases in WebDriverIO mistyping.
35-
await searchBox.addValue(regexEscape(example.substring(0, 3)));
37+
38+
await app.waitUntil(
39+
async () => {
40+
await searchBox.setValue(searchString);
41+
return (await searchBox.getText()) === searchString;
42+
},
43+
{
44+
interval: 1500,
45+
timeout: 5000,
46+
timeoutMsg: `Unable to enter correct search text into test searchbox.`,
47+
},
48+
);
49+
50+
// We cannot just click on exampleButton, since it it is likely off screen.
51+
// So we first search for the item hopfully causing the item to be one of the few remaining in the list - and therefore onscreen
52+
// Ideally we'd either use UIA to invoke the specific item, or ensure that the item is within view
53+
// Once we have those UIA patterns implemented we should update this logic.
3654
const exampleButton = await app.findElementByTestID(example);
3755
await exampleButton.waitForDisplayed({timeout: 5000});
3856
await exampleButton.click();

packages/playground/windows/playground-composition/Playground-Composition.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,13 @@ struct WindowData {
165165
viewOptions.ComponentName(appName);
166166
auto windowData = WindowData::GetFromWindow(hwnd);
167167

168-
if (!m_compRootView) {
169-
if (windowData->m_useLiftedComposition) {
170-
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView(g_liftedCompositor);
171-
} else {
172-
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView();
173-
}
168+
if (m_compRootView)
169+
break;
170+
171+
if (windowData->m_useLiftedComposition) {
172+
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView(g_liftedCompositor);
173+
} else {
174+
m_compRootView = winrt::Microsoft::ReactNative::CompositionRootView();
174175
}
175176

176177
m_compRootView.ReactViewHost(

vnext/Microsoft.ReactNative/CompositionSwitcher.idl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ namespace Microsoft.ReactNative.Composition
9696
[experimental]
9797
interface IActivityVisual requires IVisual
9898
{
99-
void Color(Windows.UI.Color color);
99+
void Brush(IBrush brush);
100100
}
101101

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

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

124124
[webhosthidden]

vnext/Microsoft.ReactNative/Fabric/ComponentView.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <react/renderer/components/view/ViewProps.h>
1212
#include <react/renderer/core/LayoutMetrics.h>
1313

14+
#include <Fabric/Composition/Theme.h>
1415
#include <winrt/Microsoft.ReactNative.Composition.Input.h>
1516

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

29+
enum class ClipState : std::uint_fast8_t {
30+
NoClip = 0,
31+
PartialClip = 1,
32+
FullyClipped = 2,
33+
};
34+
2835
DEFINE_ENUM_FLAG_OPERATORS(RNComponentViewUpdateMask);
2936

3037
struct RootComponentView;
@@ -62,11 +69,16 @@ struct IComponentView {
6269
virtual RootComponentView *rootComponentView() noexcept = 0;
6370
virtual void parent(IComponentView *parent) noexcept = 0;
6471
virtual IComponentView *parent() const noexcept = 0;
72+
virtual void theme(const std::shared_ptr<Composition::Theme> &theme) noexcept = 0;
73+
virtual std::shared_ptr<Composition::Theme> &theme() const noexcept = 0;
74+
virtual void onThemeChanged() noexcept = 0;
6575
virtual const std::vector<IComponentView *> &children() const noexcept = 0;
6676
// Run fn on all children of this node until fn returns true
6777
// returns true if the fn ever returned true
6878
virtual bool runOnChildren(bool forward, Mso::Functor<bool(IComponentView &)> &fn) noexcept = 0;
6979
virtual RECT getClientRect() const noexcept = 0;
80+
// The offset from this elements parent to its children (accounts for things like scroll position)
81+
virtual facebook::react::Point getClientOffset() const noexcept = 0;
7082
virtual void onFocusLost() noexcept = 0;
7183
virtual void onFocusGained() noexcept = 0;
7284
virtual void onPointerEntered(
@@ -101,8 +113,13 @@ struct IComponentView {
101113
facebook::react::Point &localPt,
102114
bool ignorePointerEvents = false) const noexcept = 0;
103115
virtual winrt::IInspectable EnsureUiaProvider() noexcept = 0;
116+
virtual std::optional<std::string> getAcccessiblityValue() noexcept = 0;
117+
virtual void setAcccessiblityValue(std::string &&value) noexcept = 0;
118+
virtual bool getAcccessiblityIsReadOnly() noexcept = 0;
119+
104120
// Notify up the tree to bring the rect into view by scrolling as needed
105121
virtual void StartBringIntoView(BringIntoViewOptions &&args) noexcept = 0;
122+
virtual ClipState getClipState() noexcept = 0;
106123
};
107124

108125
// Run fn on all nodes of the component view tree starting from this one until fn returns true

vnext/Microsoft.ReactNative/Fabric/Composition/AbiCompositionViewComponentView.cpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,26 @@
88

99
#include <Fabric/DWriteHelpers.h>
1010
#include "CompositionDynamicAutomationProvider.h"
11+
#include "RootComponentView.h"
1112
#include "Unicode.h"
1213

1314
namespace Microsoft::ReactNative {
1415

1516
AbiCompositionViewComponentView::AbiCompositionViewComponentView(
16-
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
17+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
1718
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
1819
facebook::react::Tag tag,
1920
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder)
20-
: Super(compContext, tag), m_builder(builder) {
21+
: Super(compContext, tag, reactContext, CompositionComponentViewFeatures::Default), m_builder(builder) {
2122
static auto const defaultProps = std::make_shared<AbiViewProps const>();
2223
m_props = defaultProps;
23-
m_handle = Builder().CreateView(reactContext, compContext);
24+
m_handle = Builder().CreateView(reactContext.Handle(), compContext);
2425
m_visual = Builder().CreateVisual(m_handle);
2526
OuterVisual().InsertAt(m_visual, 0);
2627
}
2728

2829
std::shared_ptr<AbiCompositionViewComponentView> AbiCompositionViewComponentView::Create(
29-
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
30+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
3031
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
3132
facebook::react::Tag tag,
3233
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept {
@@ -60,7 +61,7 @@ void AbiCompositionViewComponentView::updateProps(
6061
updateAccessibilityProps(oldViewProps, newViewProps);
6162
// updateShadowProps(oldViewProps, newViewProps, m_visual);
6263
// updateTransformProps(oldViewProps, newViewProps, m_visual);
63-
updateBorderProps(oldViewProps, newViewProps);
64+
Super::updateProps(props, oldProps);
6465

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

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

77-
updateBorderLayoutMetrics(layoutMetrics, *m_props);
78+
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
7879

7980
winrt::Microsoft::ReactNative::Composition::LayoutMetrics lm;
8081
Builder().UpdateLayoutMetrics(
@@ -84,21 +85,15 @@ void AbiCompositionViewComponentView::updateLayoutMetrics(
8485
layoutMetrics.frame.size.width,
8586
layoutMetrics.frame.size.height},
8687
layoutMetrics.pointScaleFactor});
87-
88-
m_layoutMetrics = layoutMetrics;
8988
}
9089

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

9594
void AbiCompositionViewComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {
95+
Super::finalizeUpdates(updateMask);
9696
Builder().FinalizeUpdates(m_handle);
97-
98-
if (m_needsBorderUpdate) {
99-
m_needsBorderUpdate = false;
100-
UpdateSpecialBorderLayers(m_layoutMetrics, *m_props);
101-
}
10297
}
10398

10499
bool AbiCompositionViewComponentView::focusable() const noexcept {
@@ -168,7 +163,8 @@ AbiCompositionViewComponentView::supplementalComponentDescriptorProviders() noex
168163
}
169164

170165
void AbiCompositionViewComponentView::prepareForRecycle() noexcept {}
171-
facebook::react::Props::Shared AbiCompositionViewComponentView::props() noexcept {
166+
167+
facebook::react::SharedViewProps AbiCompositionViewComponentView::viewProps() noexcept {
172168
return m_props;
173169
}
174170

vnext/Microsoft.ReactNative/Fabric/Composition/AbiCompositionViewComponentView.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct AbiCompositionViewComponentView : CompositionBaseComponentView {
1919
using Super = CompositionBaseComponentView;
2020

2121
[[nodiscard]] static std::shared_ptr<AbiCompositionViewComponentView> Create(
22-
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
22+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
2323
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
2424
facebook::react::Tag tag,
2525
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder) noexcept;
@@ -64,14 +64,14 @@ struct AbiCompositionViewComponentView : CompositionBaseComponentView {
6464
const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept override;
6565
std::vector<facebook::react::ComponentDescriptorProvider> supplementalComponentDescriptorProviders() noexcept
6666
override;
67-
facebook::react::Props::Shared props() noexcept override;
67+
facebook::react::SharedViewProps viewProps() noexcept override;
6868
facebook::react::Tag hitTest(facebook::react::Point pt, facebook::react::Point &localPt, bool ignorePointerEvents)
6969
const noexcept override;
7070
winrt::Microsoft::ReactNative::Composition::IVisual Visual() const noexcept override;
7171

7272
private:
7373
AbiCompositionViewComponentView(
74-
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
74+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
7575
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
7676
facebook::react::Tag tag,
7777
winrt::Microsoft::ReactNative::IReactViewComponentBuilder builder);

vnext/Microsoft.ReactNative/Fabric/Composition/ActivityIndicatorComponentView.cpp

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <Windows.UI.Composition.h>
1010
#include <Windows.h>
1111
#include "CompositionContextHelper.h"
12+
#include "RootComponentView.h"
1213

1314
namespace Microsoft::ReactNative {
1415

@@ -24,7 +25,7 @@ ActivityIndicatorComponentView::ActivityIndicatorComponentView(
2425
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
2526
facebook::react::Tag tag,
2627
winrt::Microsoft::ReactNative::ReactContext const &reactContext)
27-
: Super(compContext, tag), m_context(reactContext) {
28+
: Super(compContext, tag, reactContext, CompositionComponentViewFeatures::Default) {
2829
m_props = std::make_shared<facebook::react::ActivityIndicatorViewProps const>();
2930
}
3031

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

48+
void ActivityIndicatorComponentView::updateProgressColor(const facebook::react::SharedColor &color) noexcept {
49+
if (color) {
50+
m_ActivityIndicatorVisual.Brush(theme()->Brush(*color));
51+
} else {
52+
m_ActivityIndicatorVisual.Brush(theme()->PlatformBrush("ProgressRingForegroundTheme"));
53+
}
54+
}
55+
4756
void ActivityIndicatorComponentView::updateProps(
4857
facebook::react::Props::Shared const &props,
4958
facebook::react::Props::Shared const &oldProps) noexcept {
@@ -53,15 +62,16 @@ void ActivityIndicatorComponentView::updateProps(
5362
ensureVisual();
5463

5564
// update color if needed
56-
if (newViewProps->color && (!oldProps || newViewProps->color != oldViewProps->color)) {
57-
m_ActivityIndicatorVisual.Color(newViewProps->color.AsWindowsColor());
65+
if (!oldProps || newViewProps->color != oldViewProps->color) {
66+
updateProgressColor(newViewProps->color);
5867
}
5968

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

64-
updateBorderProps(*oldViewProps, *newViewProps);
73+
Super::updateProps(props, oldProps);
74+
6575
m_props = std::static_pointer_cast<facebook::react::ViewProps const>(props);
6676
}
6777

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

82-
updateBorderLayoutMetrics(layoutMetrics, *m_props);
83-
m_layoutMetrics = layoutMetrics;
84-
85-
UpdateCenterPropertySet();
92+
Super::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
8693
m_visual.Size(
8794
{layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
8895
layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
8996
}
9097

91-
void ActivityIndicatorComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {}
92-
9398
void ActivityIndicatorComponentView::prepareForRecycle() noexcept {}
9499

95-
facebook::react::Props::Shared ActivityIndicatorComponentView::props() noexcept {
100+
facebook::react::SharedViewProps ActivityIndicatorComponentView::viewProps() noexcept {
96101
return m_props;
97102
}
98103

@@ -126,6 +131,10 @@ winrt::Microsoft::ReactNative::Composition::IVisual ActivityIndicatorComponentVi
126131
return m_visual;
127132
}
128133

134+
void ActivityIndicatorComponentView::onThemeChanged() noexcept {
135+
updateProgressColor(std::static_pointer_cast<const facebook::react::ActivityIndicatorViewProps>(m_props)->color);
136+
}
137+
129138
bool ActivityIndicatorComponentView::focusable() const noexcept {
130139
return false;
131140
}

0 commit comments

Comments
 (0)