Skip to content

Commit

Permalink
Adds test native module project for Playground (#10838)
Browse files Browse the repository at this point in the history
* Adds test native module project for Playground

The GitHub repo Playground projects are missing examples that exercise
3rd party APIs. While there are examples that use 3rd party extensions
from NPM, there are some 3rd party APIs that are not used in ecosystem
packages (yet).

This native modules project gives us a place to prototype custom view
managers and native modules.

* Use LayoutService in NativeModule example

Adds example that uses the LayoutService API that will be available in
v0.71

* yarn format

* Remove unnecessary NuGet package reference

* Add PlaygroundNativeModules project to playground.sln

* yarn lint:fix
  • Loading branch information
rozele committed Nov 10, 2022
1 parent 80be786 commit 0f12a02
Show file tree
Hide file tree
Showing 26 changed files with 774 additions and 3 deletions.
62 changes: 62 additions & 0 deletions packages/playground/Samples/nativeLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
* @format
*/
import React from 'react';
import {AppRegistry, View, Text, requireNativeComponent} from 'react-native';

const GridView: any = requireNativeComponent('PlaygroundGridView');
const GridItemView: any = requireNativeComponent('PlaygroundGridItemView');

function Bootstrap() {
return (
<GridView
style={{width: 200, height: 200}}
columns={['*', '*']}
rows={['*', '*']}>
<GridItemView gridRow={0} gridColumn={0}>
<View
style={{
backgroundColor: '#F25022',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>1</Text>
</View>
</GridItemView>
<GridItemView gridRow={0} gridColumn={1}>
<View
style={{
backgroundColor: '#7FBA00',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>2</Text>
</View>
</GridItemView>
<GridItemView gridRow={1} gridColumn={0}>
<View
style={{
backgroundColor: '#00A4EF',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>3</Text>
</View>
</GridItemView>
<GridItemView gridRow={1} gridColumn={1}>
<View
style={{
backgroundColor: '#FFB900',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>4</Text>
</View>
</GridItemView>
</GridView>
);
}

AppRegistry.registerComponent('Bootstrap', () => Bootstrap);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "GridItemView.h"
#include "GridItemView.g.cpp"

namespace winrt {
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace xaml;
using namespace xaml::Controls;
} // namespace winrt

namespace winrt::PlaygroundNativeModules::implementation {

winrt::Size GridItemView::ArrangeOverride(winrt::Size availableSize) {
const auto desiredSize = Super::ArrangeOverride(availableSize);
if (Children().Size() > 0) {
if (const auto child = Children().GetAt(0).try_as<xaml::FrameworkElement>()) {
const auto reactTag = React::XamlHelper::GetReactTag(child);
if (reactTag != -1) {
React::LayoutService::FromContext(m_reactContext).ApplyLayout(reactTag, desiredSize.Width, desiredSize.Height);
}
}
}
return desiredSize;
}

} // namespace winrt::PlaygroundNativeModules::implementation
26 changes: 26 additions & 0 deletions packages/playground/windows/PlaygroundNativeModules/GridItemView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once

#include "GridItemView.g.h"
#include "NativeModules.h"
#include "winrt/Microsoft.ReactNative.h"

namespace winrt::PlaygroundNativeModules::implementation {

class GridItemView : public GridItemViewT<GridItemView> {
using Super = GridItemViewT<GridItemView>;

public:
GridItemView(Microsoft::ReactNative::IReactContext const &reactContext) : m_reactContext{reactContext} {}
virtual winrt::Windows::Foundation::Size ArrangeOverride(winrt::Windows::Foundation::Size availableSize);

private:
Microsoft::ReactNative::IReactContext m_reactContext{nullptr};
};
} // namespace winrt::PlaygroundNativeModules::implementation

namespace winrt::PlaygroundNativeModules::factory_implementation {
struct GridItemView : GridItemViewT<GridItemView, implementation::GridItemView> {};
} // namespace winrt::PlaygroundNativeModules::factory_implementation
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <NamespaceRedirect.h>

namespace PlaygroundNativeModules {

[default_interface]
runtimeclass GridItemView : XAML_NAMESPACE.Controls.Grid {
GridItemView(Microsoft.ReactNative.IReactContext context);
};
} // namespace PlaygroundNativeModules
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "GridItemViewManager.h"
#include "GridItemView.h"
#include "JSValue.h"
#include "JSValueXaml.h"

namespace winrt {
using namespace Windows::Foundation::Collections;
} // namespace winrt

namespace winrt::PlaygroundNativeModules {

winrt::hstring GridItemViewManager::Name() noexcept {
return L"PlaygroundGridItemView";
}

xaml::FrameworkElement GridItemViewManager::CreateView() noexcept {
return winrt::PlaygroundNativeModules::GridItemView(m_reactContext.Handle());
}

React::IReactContext GridItemViewManager::ReactContext() noexcept {
return m_reactContext.Handle();
}

void GridItemViewManager::ReactContext(React::IReactContext reactContext) noexcept {
m_reactContext = reactContext;
}

void GridItemViewManager::AddView(
xaml::FrameworkElement const &parent,
xaml::UIElement const &child,
int64_t index) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
if (grid.Children().Size() > 0 || index != 0) {
m_reactContext.CallJSFunction(L"RCTLog", L"logToConsole", "warn", "GridItem only supports one child.");
} else {
grid.Children().InsertAt(static_cast<uint32_t>(index), child);
}
}
}

void GridItemViewManager::RemoveAllChildren(xaml::FrameworkElement const &parent) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
grid.Children().Clear();
}
}

void GridItemViewManager::RemoveChildAt(xaml::FrameworkElement const &parent, int64_t index) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
if (index == 0) {
grid.Children().RemoveAt(static_cast<uint32_t>(index));
}
}
}

void GridItemViewManager::ReplaceChild(
xaml::FrameworkElement const &parent,
xaml::UIElement const &oldChild,
xaml::UIElement const &newChild) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
uint32_t index;
if (grid.Children().IndexOf(oldChild, index)) {
grid.Children().RemoveAt(index);
grid.Children().InsertAt(index, newChild);
}
}
}

winrt::IMapView<winrt::hstring, React::ViewManagerPropertyType> GridItemViewManager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<winrt::hstring, React::ViewManagerPropertyType>();
nativeProps.Insert(L"gridRow", React::ViewManagerPropertyType::Number);
nativeProps.Insert(L"gridColumn", React::ViewManagerPropertyType::Number);
return nativeProps.GetView();
}

void GridItemViewManager::UpdateProperties(
xaml::FrameworkElement const &view,
winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader) noexcept {
React::JSValueObject propertyMap = React::JSValueObject::ReadFrom(propertyMapReader);
for (const auto &pair : propertyMap) {
const auto &propertyName = winrt::to_hstring(pair.first);
const auto &propertyValue = pair.second;

if (propertyName == L"gridRow") {
xaml::Controls::Grid::SetRow(view, propertyValue.AsInt32());
} else if (propertyName == L"gridColumn") {
xaml::Controls::Grid::SetColumn(view, propertyValue.AsInt32());
} else if (propertyName == L"backgroundColor") {
view.as<xaml::Controls::Grid>().Background(propertyValue.To<xaml::Media::Brush>());
}
}
}

} // namespace winrt::PlaygroundNativeModules
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#pragma once
#include "NativeModules.h"
#include "winrt/Microsoft.ReactNative.h"

namespace winrt::PlaygroundNativeModules {

class GridItemViewManager : public winrt::implements<
GridItemViewManager,
React::IViewManager,
React::IViewManagerWithReactContext,
React::IViewManagerWithChildren,
React::IViewManagerWithNativeProperties,
React::IViewManagerRequiresNativeLayout> {
public:
// IViewManager
winrt::hstring Name() noexcept;

xaml::FrameworkElement CreateView() noexcept;

// IViewManagerWithReactContext
React::IReactContext ReactContext() noexcept;
void ReactContext(React::IReactContext reactContext) noexcept;

// IViewManagerWithChildren
void AddView(xaml::FrameworkElement const &parent, xaml::UIElement const &child, int64_t index) noexcept;
void RemoveAllChildren(xaml::FrameworkElement const &parent) noexcept;
void RemoveChildAt(xaml::FrameworkElement const &parent, int64_t index) noexcept;
void ReplaceChild(
xaml::FrameworkElement const &parent,
xaml::UIElement const &oldChild,
xaml::UIElement const &newChild) noexcept;

// IViewManagerWithNativeProperties
winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, React::ViewManagerPropertyType>
NativeProps() noexcept;

void UpdateProperties(xaml::FrameworkElement const &view, React::IJSValueReader const &propertyMapReader) noexcept;

// IViewManagerRequiresNativeLayout
bool RequiresNativeLayout() const noexcept {
return true;
}

private:
winrt::Microsoft::ReactNative::ReactContext m_reactContext{nullptr};
};

} // namespace winrt::PlaygroundNativeModules
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "pch.h"
#include "GridViewManager.h"

namespace winrt::PlaygroundNativeModules {

winrt::hstring GridViewManager::Name() noexcept {
return L"PlaygroundGridView";
}

xaml::FrameworkElement GridViewManager::CreateView() noexcept {
return xaml::Controls::Grid();
}

void GridViewManager::AddView(
xaml::FrameworkElement const &parent,
xaml::UIElement const &child,
int64_t index) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
grid.Children().InsertAt(static_cast<uint32_t>(index), child);
}
}

void GridViewManager::RemoveAllChildren(xaml::FrameworkElement const &parent) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
grid.Children().Clear();
}
}

void GridViewManager::RemoveChildAt(xaml::FrameworkElement const &parent, int64_t index) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
grid.Children().RemoveAt(static_cast<uint32_t>(index));
}
}

void GridViewManager::ReplaceChild(
xaml::FrameworkElement const &parent,
xaml::UIElement const &oldChild,
xaml::UIElement const &newChild) noexcept {
if (auto const &grid = parent.try_as<xaml::Controls::Grid>()) {
uint32_t index;
if (grid.Children().IndexOf(oldChild, index)) {
grid.Children().RemoveAt(index);
grid.Children().InsertAt(index, newChild);
}
}
}

winrt::IMapView<winrt::hstring, React::ViewManagerPropertyType> GridViewManager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<winrt::hstring, React::ViewManagerPropertyType>();
nativeProps.Insert(L"rows", React::ViewManagerPropertyType::Array);
nativeProps.Insert(L"columns", React::ViewManagerPropertyType::Array);
return nativeProps.GetView();
}

xaml::GridLength GetGridLength(const winrt::Microsoft::ReactNative::JSValue &v) {
if (v.Type() == React::JSValueType::Double || v.Type() == React::JSValueType::Int64) {
return xaml::GridLengthHelper::FromValueAndType(v.AsDouble(), xaml::GridUnitType::Pixel);
} else if (v.Type() == React::JSValueType::String) {
auto str = v.AsString();
double units = 1;
xaml::GridUnitType unitType = xaml::GridUnitType::Pixel;
if (str.back() == '*') {
unitType = xaml::GridUnitType::Star;
str.pop_back();
if (str.length() > 0) {
units = std::stod(str);
}
} else if (str == "auto") {
unitType = xaml::GridUnitType::Auto;
} else {
units = std::stod(str);
}
return xaml::GridLengthHelper::FromValueAndType(units, unitType);
}
return xaml::GridLengthHelper::FromValueAndType(1, xaml::GridUnitType::Auto);
}

void GridViewManager::UpdateProperties(
xaml::FrameworkElement const &view,
winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader) noexcept {
React::JSValueObject propertyMap = React::JSValueObject::ReadFrom(propertyMapReader);
for (const auto &pair : propertyMap) {
const auto &propertyName = winrt::to_hstring(pair.first);
const auto &propertyValue = pair.second;

if (propertyName == L"rows") {
const auto grid = view.as<xaml::Controls::Grid>();
for (const auto &row : propertyValue.AsArray()) {
xaml::Controls::RowDefinition rowDefinition{};
rowDefinition.Height(GetGridLength(row));
grid.RowDefinitions().Append(rowDefinition);
}
} else if (propertyName == L"columns") {
const auto grid = view.as<xaml::Controls::Grid>();
for (const auto &column : propertyValue.AsArray()) {
xaml::Controls::ColumnDefinition columnDefinition{};
columnDefinition.Width(GetGridLength(column));
grid.ColumnDefinitions().Append(columnDefinition);
}
}
}
}

} // namespace winrt::PlaygroundNativeModules

0 comments on commit 0f12a02

Please sign in to comment.