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

prototype(FastText): Add option to use TextBlock for simple text #1256

Merged
merged 6 commits into from
Aug 14, 2018
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
275 changes: 275 additions & 0 deletions Libraries/Text/Text.windows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule Text
* @flow
* @format
*/
'use strict';

const React = require('React');
const ReactNative = require('ReactNative');
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
const TextPropTypes = require('TextPropTypes');
const Touchable = require('Touchable');
const UIManager = require('UIManager');

const createReactNativeComponentClass = require('createReactNativeComponentClass');
const mergeFast = require('mergeFast');
const processColor = require('processColor');
const {ViewContextTypes} = require('ViewContext');

import type {PressEvent} from 'CoreEventTypes';
import type {TextProps} from 'TextProps';
import type {ViewChildContext} from 'ViewContext';

type State = {
isHighlighted: boolean,
};

type RectOffset = {
top: number,
left: number,
right: number,
bottom: number,
};

const PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};

const viewConfig = {
validAttributes: mergeFast(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
numberOfLines: true,
ellipsizeMode: true,
allowFontScaling: true,
disabled: true,
selectable: true,
selectionColor: true,
adjustsFontSizeToFit: true,
minimumFontScale: true,
textBreakStrategy: true,
}),
uiViewClassName: 'RCTText',
};

const simpleViewConfig = {
validAttributes: mergeFast(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
numberOfLines: true,
ellipsizeMode: true,
allowFontScaling: true,
disabled: true,
selectable: true,
selectionColor: true,
adjustsFontSizeToFit: true,
minimumFontScale: true,
textBreakStrategy: true,
text: true,
}),
uiViewClassName: 'RCTSimpleText',
};

/**
* A React component for displaying text.
*
* See https://facebook.github.io/react-native/docs/text.html
*/
class Text extends ReactNative.NativeComponent<TextProps, State> {
static propTypes = TextPropTypes;
static childContextTypes = ViewContextTypes;
static contextTypes = ViewContextTypes;

static defaultProps = {
accessible: true,
allowFontScaling: true,
ellipsizeMode: 'tail',
};

state = mergeFast(Touchable.Mixin.touchableGetInitialState(), {
isHighlighted: false,
});

viewConfig = viewConfig;

getChildContext(): ViewChildContext {
return {
isInAParentText: true,
};
}

_handlers: ?Object;

_hasPressHandler(): boolean {
return !!this.props.onPress || !!this.props.onLongPress;
}
/**
* These are assigned lazily the first time the responder is set to make plain
* text nodes as cheap as possible.
*/
touchableHandleActivePressIn: ?Function;
touchableHandleActivePressOut: ?Function;
touchableHandlePress: ?Function;
touchableHandleLongPress: ?Function;
touchableHandleResponderGrant: ?Function;
touchableHandleResponderMove: ?Function;
touchableHandleResponderRelease: ?Function;
touchableHandleResponderTerminate: ?Function;
touchableHandleResponderTerminationRequest: ?Function;
touchableGetPressRectOffset: ?Function;

render(): React.Element<any> {
let newProps = this.props;
if (this.props.onStartShouldSetResponder || this._hasPressHandler()) {
if (!this._handlers) {
this._handlers = {
onStartShouldSetResponder: (): boolean => {
const shouldSetFromProps =
this.props.onStartShouldSetResponder &&
this.props.onStartShouldSetResponder();
const setResponder = shouldSetFromProps || this._hasPressHandler();
if (setResponder && !this.touchableHandleActivePressIn) {
// Attach and bind all the other handlers only the first time a touch
// actually happens.
for (const key in Touchable.Mixin) {
if (typeof Touchable.Mixin[key] === 'function') {
(this: any)[key] = Touchable.Mixin[key].bind(this);
}
}
this.touchableHandleActivePressIn = () => {
if (
this.props.suppressHighlighting ||
!this._hasPressHandler()
) {
return;
}
this.setState({
isHighlighted: true,
});
};

this.touchableHandleActivePressOut = () => {
if (
this.props.suppressHighlighting ||
!this._hasPressHandler()
) {
return;
}
this.setState({
isHighlighted: false,
});
};

this.touchableHandlePress = (e: PressEvent) => {
this.props.onPress && this.props.onPress(e);
};

this.touchableHandleLongPress = (e: PressEvent) => {
this.props.onLongPress && this.props.onLongPress(e);
};

this.touchableGetPressRectOffset = function(): RectOffset {
return this.props.pressRetentionOffset || PRESS_RECT_OFFSET;
};
}
return setResponder;
},
onResponderGrant: function(e: SyntheticEvent<>, dispatchID: string) {
// $FlowFixMe TouchableMixin handlers couldn't actually be null
this.touchableHandleResponderGrant(e, dispatchID);
this.props.onResponderGrant &&
this.props.onResponderGrant.apply(this, arguments);
}.bind(this),
onResponderMove: function(e: SyntheticEvent<>) {
// $FlowFixMe TouchableMixin handlers couldn't actually be null
this.touchableHandleResponderMove(e);
this.props.onResponderMove &&
this.props.onResponderMove.apply(this, arguments);
}.bind(this),
onResponderRelease: function(e: SyntheticEvent<>) {
// $FlowFixMe TouchableMixin handlers couldn't actually be null
this.touchableHandleResponderRelease(e);
this.props.onResponderRelease &&
this.props.onResponderRelease.apply(this, arguments);
}.bind(this),
onResponderTerminate: function(e: SyntheticEvent<>) {
// $FlowFixMe TouchableMixin handlers couldn't actually be null
this.touchableHandleResponderTerminate(e);
this.props.onResponderTerminate &&
this.props.onResponderTerminate.apply(this, arguments);
}.bind(this),
onResponderTerminationRequest: function(): boolean {
// Allow touchable or props.onResponderTerminationRequest to deny
// the request
// $FlowFixMe TouchableMixin handlers couldn't actually be null
var allowTermination = this.touchableHandleResponderTerminationRequest();
if (allowTermination && this.props.onResponderTerminationRequest) {
allowTermination = this.props.onResponderTerminationRequest.apply(
this,
arguments,
);
}
return allowTermination;
}.bind(this),
};
}
newProps = {
...this.props,
...this._handlers,
isHighlighted: this.state.isHighlighted,
};
}
if (newProps.selectionColor != null) {
newProps = {
...newProps,
selectionColor: processColor(newProps.selectionColor),
};
}
if (Touchable.TOUCH_TARGET_DEBUG && newProps.onPress) {
newProps = {
...newProps,
style: [this.props.style, {color: 'magenta'}],
};
}
if (this.context.isInAParentText) {
return <RCTVirtualText {...newProps} />;
} else {
if (RCTSimpleText && typeof newProps.children === 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be an array of strings.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@reseul - not sure if we are supposed to handle the array of strings. I don't know what the concatenation handling should be, whether we just inject a space, newline, etc. Also, I imagine this can vary based on culture/i18n. I'd like to keep this as a single space for now and force the client to specifically concatenate string arrays if they want text perf optimization.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rozele it's ok. We've been using this fast text for some months, but I'm not sure whether we have that case. If we do. we're going to reconcile on our end.

let simpleProps = {};
Object.assign(simpleProps, newProps);
simpleProps.text = simpleProps.children;
delete simpleProps.children;
return <RCTSimpleText {...simpleProps} />;
}

return <RCTText {...newProps} />;
}
}
}

var RCTText = createReactNativeComponentClass(
viewConfig.uiViewClassName,
() => viewConfig,
);
var RCTVirtualText = RCTText;
var RCTSimpleText;

if (UIManager.RCTVirtualText) {
RCTVirtualText = createReactNativeComponentClass('RCTVirtualText', () => ({
validAttributes: mergeFast(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
}),
uiViewClassName: 'RCTVirtualText',
}));
}

if (UIManager.RCTSimpleText) {
RCTSimpleText = createReactNativeComponentClass(
simpleViewConfig.uiViewClassName,
() => simpleViewConfig,
);
}

module.exports = Text;
1 change: 1 addition & 0 deletions ReactWindows/ReactNative.Net46/ReactNative.Net46.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
<Compile Include="Views\Scroll\ScrollView.cs" />
<Compile Include="Views\Slider\ReactSliderManager.cs" />
<Compile Include="Views\TextInput\PlaceholderAdorner.cs" />
<Compile Include="Views\Text\ReactSimpleTextViewManager.cs" />
<Compile Include="Views\Text\ReactTextCompoundView.cs" />
Copy link
Contributor

@reseul reseul May 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rozele Where's the shadow node?

[edit]: Oh, it's a dummy implementation for WPF?

<Compile Include="Views\Text\ReactTextShadowNode.cs" />
<Compile Include="Views\Text\ReactTextViewManager.cs" />
Expand Down
1 change: 1 addition & 0 deletions ReactWindows/ReactNative.Net46/Shell/MainReactPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public IReadOnlyList<IViewManager> CreateViewManagers(
{
return new List<IViewManager>
{
new ReactSimpleTextViewManager(),
//new ReactFlipViewManager(),
new ReactImageManager(),
new ReactProgressBarViewManager(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Windows.Controls;

namespace ReactNative.Views.Text
{
/// <summary>
/// The view manager for simple text views.
/// </summary>
public class ReactSimpleTextViewManager : ReactTextViewManager
{
/// <summary>
/// Name of the view manager.
/// </summary>
public override string Name
{
get
{
return "RCTSimpleText";
}
}

/// <summary>
/// Gets the number of children for the view.
/// </summary>
/// <param name="parent">The view parent.</param>
/// <returns>The number of children.</returns>
public override int GetChildCount(TextBlock parent)
{
return 0;
}

/// <summary>
/// Removes the children from the view.
/// </summary>
/// <param name="parent">The view parent.</param>
public override void RemoveAllChildren(TextBlock parent)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using ReactNative.UIManager;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

Expand All @@ -13,7 +14,8 @@ public int GetReactTagAtPoint(UIElement reactView, Point point)
{
var richTextBlock = reactView.As<TextBlock>();
var textPointer = richTextBlock.GetPositionFromPoint(point, true);
return textPointer.Parent.GetTag();
var parentView = RootViewHelper.GetReactViewHierarchy(textPointer.Parent).First();
return parentView.GetTag();
}
}
}
24 changes: 22 additions & 2 deletions ReactWindows/ReactNative.Net46/Views/Text/ReactTextShadowNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class ReactTextShadowNode : LayoutShadowNode

private string _fontFamily;

private string _text;

/// <summary>
/// Instantiates a <see cref="ReactTextShadowNode"/>.
/// </summary>
Expand All @@ -39,6 +41,21 @@ public ReactTextShadowNode()
MeasureText(this, node, width, widthMode, height, heightMode);
}

/// <summary>
/// Sets the text for the node.
/// </summary>
/// <param name="text">The text.</param>
[ReactProp("text")]
public void SetText(string text)
{
var nonNullText = text ?? "";
if (_text != nonNullText)
{
_text = nonNullText;
MarkUpdated();
}
}

/// <summary>
/// Sets the font size for the node.
/// </summary>
Expand Down Expand Up @@ -225,8 +242,11 @@ public void UpdateTextBlock(TextBlock textBlock)

private void UpdateTextBlockCore(TextBlock textBlock, bool measureOnly)
{
//textBlock.CharacterSpacing = _letterSpacing;
//textBlock.MaxLines = _numberOfLines;
if (ChildCount == 0 && _text != null)
{
textBlock.Text = _text;
}

textBlock.LineHeight = _lineHeight != 0 ? _lineHeight : double.NaN;
textBlock.TextAlignment = _textAlignment;
textBlock.FontSize = _fontSize ?? 15;
Expand Down
Loading