Skip to content
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
23 changes: 21 additions & 2 deletions apps/toolbox/src/pages/labels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Page, Observable, EventData } from '@nativescript/core';
import { Page, Observable, EventData, Label, Color } from '@nativescript/core';

let page: Page;

Expand All @@ -7,4 +7,23 @@ export function navigatingTo(args: EventData) {
page.bindingContext = new SampleData();
}

export class SampleData extends Observable {}
export class SampleData extends Observable {
strokeLabel: Label;

loadedStrokeLabel(args) {
this.strokeLabel = args.object;
}

toggleStrokeStyle() {
if (this.strokeLabel.style.textStroke) {
this.strokeLabel.style.color = new Color('black');
this.strokeLabel.style.textStroke = null;
} else {
this.strokeLabel.style.color = new Color('white');
this.strokeLabel.style.textStroke = {
color: new Color('black'),
width: { value: 2, unit: 'px' },
};
}
}
}
3 changes: 3 additions & 0 deletions apps/toolbox/src/pages/labels.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<GridLayout marginTop="10" borderWidth="1" borderColor="#efefef" height="60" paddingLeft="5">
<Button text="Test Button text-overflow: initial, this should be long sentence and truncated in the middle with ellipsis." textOverflow="initial" whiteSpace="nowrap" />
</GridLayout>
<GridLayout marginTop="10" height="60" paddingLeft="5" tap="{{toggleStrokeStyle}}">
<Label text=" text-stroke" style="text-stroke: 2px black; color: #fff; font-size: 35; font-weight: bold; font-family:Arial, Helvetica, sans-serif" loaded="{{loadedStrokeLabel}}"/>
</GridLayout>
<Label text="maxLines 2" fontWeight="bold" marginTop="10" />
<Label
text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
Expand Down
Binary file modified packages/core/platforms/android/widgets-release.aar
Binary file not shown.
2 changes: 2 additions & 0 deletions packages/core/platforms/ios/src/UIView+NativeScript.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@

-(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details letterSpacing:(CGFloat)letterSpacing lineHeight:(CGFloat)lineHeight;

-(void)nativeScriptSetFormattedTextStroke:(CGFloat)width color:(UIColor*)color;

@end
15 changes: 15 additions & 0 deletions packages/core/platforms/ios/src/UIView+NativeScript.m
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,19 @@ -(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details
((UILabel*)self).attributedText = attrText;
}
}

-(void)nativeScriptSetFormattedTextStroke:(CGFloat)width color:(UIColor*)color {
if (width > 0) {
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] initWithAttributedString:((UILabel*)self).attributedText];
[attrText addAttribute:NSStrokeWidthAttributeName value:[NSNumber numberWithFloat:width] range:(NSRange){
0,
attrText.length
}];
[attrText addAttribute:NSStrokeColorAttributeName value:color range:(NSRange){
0,
attrText.length
}];
((UILabel*)self).attributedText = attrText;
}
}
@end
2 changes: 1 addition & 1 deletion packages/core/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export { CSSHelper } from './styling/css-selector';

export { Switch } from './switch';
export { TabView, TabViewItem } from './tab-view';
export { TextBase, getTransformedText, letterSpacingProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, whiteSpaceProperty, textOverflowProperty, lineHeightProperty } from './text-base';
export { TextBase, getTransformedText, letterSpacingProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, whiteSpaceProperty, textOverflowProperty, lineHeightProperty } from './text-base';
export { FormattedString } from './text-base/formatted-string';
export { Span } from './text-base/span';
export { TextField } from './text-field';
Expand Down
12 changes: 3 additions & 9 deletions packages/core/ui/label/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { CoreTypes } from '../../core-types';

export * from '../text-base';

let TextView: typeof android.widget.TextView;

@CSSType('Label')
export class Label extends TextBase implements LabelDefinition {
nativeViewProtected: android.widget.TextView;
nativeTextViewProtected: android.widget.TextView;
nativeViewProtected: org.nativescript.widgets.StyleableTextView;
nativeTextViewProtected: org.nativescript.widgets.StyleableTextView;

get textWrap(): boolean {
return this.style.whiteSpace === 'normal';
Expand All @@ -27,11 +25,7 @@ export class Label extends TextBase implements LabelDefinition {

@profile
public createNativeView() {
if (!TextView) {
TextView = android.widget.TextView;
}

return new TextView(this._context);
return new org.nativescript.widgets.StyleableTextView(this._context);
}

public initNativeView(): void {
Expand Down
83 changes: 29 additions & 54 deletions packages/core/ui/styling/css-shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@ const LENGTH_RE = /^-?[0-9]+[a-zA-Z%]*?$/;
*/
const isLength = (v) => v === '0' || LENGTH_RE.test(v);

/**
* Parse a string into ShadowCSSValues
* Supports any valid css box/text shadow combination.
*
* inspired by https://github.com/jxnblk/css-box-shadow/blob/master/index.js (MIT License)
*
* @param value
*/
export function parseCSSShadow(value: string): ShadowCSSValues {
export function parseCSSShorthand(value: string): {
values: Array<CoreTypes.LengthType>;
color: string;
inset: boolean;
} {
const parts = value.trim().split(PARTS_RE);
const inset = parts.includes('inset');
const first = parts[0];
Expand All @@ -44,67 +40,46 @@ export function parseCSSShadow(value: string): ShadowCSSValues {
return null;
}

let colorRaw = 'black';
let color = 'black';
if (first && !isLength(first) && first !== 'inset') {
colorRaw = first;
color = first;
} else if (last && !isLength(last)) {
colorRaw = last;
color = last;
}
const nums = parts
const values = parts
.filter((n) => n !== 'inset')
.filter((n) => n !== colorRaw)
.filter((n) => n !== color)
.map((val) => {
try {
return Length.parse(val);
} catch (err) {
return CoreTypes.zeroLength;
}
});
const [offsetX, offsetY, blurRadius, spreadRadius] = nums;

return {
inset,
color,
values,
};
}
/**
* Parse a string into ShadowCSSValues
* Supports any valid css box/text shadow combination.
*
* inspired by https://github.com/jxnblk/css-box-shadow/blob/master/index.js (MIT License)
*
* @param value
*/
export function parseCSSShadow(value: string): ShadowCSSValues {
const data = parseCSSShorthand(value);
const [offsetX, offsetY, blurRadius, spreadRadius] = data.values;

return {
inset: data.inset,
offsetX: offsetX,
offsetY: offsetY,
blurRadius: blurRadius,
spreadRadius: spreadRadius,
color: new Color(colorRaw),
color: new Color(data.color),
};
}

// if (value.indexOf('rgb') > -1) {
// arr = value.split(' ');
// colorRaw = arr.pop();
// } else {
// arr = value.split(/[ ,]+/);
// colorRaw = arr.pop();
// }

// let offsetX: number;
// let offsetY: number;
// let blurRadius: number; // not currently in use
// let spreadRadius: number; // maybe rename this to just radius
// let color: Color = new Color(colorRaw);

// if (arr.length === 2) {
// offsetX = parseFloat(arr[0]);
// offsetY = parseFloat(arr[1]);
// } else if (arr.length === 3) {
// offsetX = parseFloat(arr[0]);
// offsetY = parseFloat(arr[1]);
// blurRadius = parseFloat(arr[2]);
// } else if (arr.length === 4) {
// offsetX = parseFloat(arr[0]);
// offsetY = parseFloat(arr[1]);
// blurRadius = parseFloat(arr[2]);
// spreadRadius = parseFloat(arr[3]);
// } else {
// throw new Error('Expected 3, 4 or 5 parameters. Actual: ' + value);
// }
// return {
// offsetX: offsetX,
// offsetY: offsetY,
// blurRadius: blurRadius,
// spreadRadius: spreadRadius,
// color: color,
// };
30 changes: 30 additions & 0 deletions packages/core/ui/styling/css-stroke.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { parseCSSStroke } from './css-stroke';
import { CoreTypes } from '../../core-types';
import { Length } from './style-properties';
import { Color } from '../../color';

describe('css-text-stroke', () => {
it('empty', () => {
const stroke = parseCSSStroke('');
expect(stroke.width).toBe(CoreTypes.zeroLength);
expect(stroke.color).toEqual(new Color('black'));
});

it('1px navy', () => {
const stroke = parseCSSStroke('1px navy');
expect(stroke.width).toEqual(Length.parse('1px'));
expect(stroke.color).toEqual(new Color('navy'));
});

it('5 green', () => {
const stroke = parseCSSStroke('5 green');
expect(stroke.width).toEqual(Length.parse('5'));
expect(stroke.color).toEqual(new Color('green'));
});

it('2px #999', () => {
const stroke = parseCSSStroke('2px #999');
expect(stroke.width).toEqual(Length.parse('2px'));
expect(stroke.color).toEqual(new Color('#999'));
});
});
23 changes: 23 additions & 0 deletions packages/core/ui/styling/css-stroke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CoreTypes } from '../../core-types';
import { Color } from '../../color';
import { parseCSSShorthand } from './css-shadow';

export interface StrokeCSSValues {
width: CoreTypes.LengthType;
color: Color;
}

/**
* Parse a string into StrokeCSSValues
* https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke
* @param value
*/
export function parseCSSStroke(value: string): StrokeCSSValues {
const data = parseCSSShorthand(value);
const [width] = data.values;

return {
width,
color: new Color(data.color),
};
}
2 changes: 2 additions & 0 deletions packages/core/ui/styling/style/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Trace } from '../../../trace';
import { CoreTypes } from '../../../core-types';
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types';
import { ShadowCSSValues } from '../css-shadow';
import { StrokeCSSValues } from '../css-stroke';

export interface CommonLayoutParams {
width: number;
Expand Down Expand Up @@ -171,6 +172,7 @@ export class Style extends Observable implements StyleDefinition {
public textDecoration: CoreTypes.TextDecorationType;
public textTransform: CoreTypes.TextTransformType;
public textShadow: ShadowCSSValues;
public textStroke: StrokeCSSValues;
public whiteSpace: CoreTypes.WhiteSpaceType;
public textOverflow: CoreTypes.TextOverflowType;

Expand Down
22 changes: 16 additions & 6 deletions packages/core/ui/text-base/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { ShadowCSSValues } from '../styling/css-shadow';
// Requires
import { Font } from '../styling/font';
import { backgroundColorProperty } from '../styling/style-properties';
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, isBold, resetSymbol } from './text-base-common';
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, isBold, resetSymbol } from './text-base-common';
import { Color } from '../../color';
import { colorProperty, fontSizeProperty, fontInternalProperty, paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../styling/style-properties';
import { StrokeCSSValues } from '../styling/css-stroke';
import { FormattedString } from './formatted-string';
import { Span } from './span';
import { CoreTypes } from '../../core-types';
import { layout } from '../../utils';
import { SDK_VERSION } from '../../utils/constants';
import { isString, isNullOrUndefined } from '../../utils/types';
import { accessibilityIdentifierProperty } from '../../accessibility/accessibility-properties';
import * as Utils from '../../utils';
import { testIDProperty } from '../../ui/core/view';

export * from './text-base-common';
Expand Down Expand Up @@ -169,9 +169,9 @@ function initializeBaselineAdjustedSpan(): void {
}

export class TextBase extends TextBaseCommon {
nativeViewProtected: android.widget.TextView;
nativeViewProtected: org.nativescript.widgets.StyleableTextView;
// @ts-ignore
nativeTextViewProtected: android.widget.TextView;
nativeTextViewProtected: org.nativescript.widgets.StyleableTextView;
private _defaultTransformationMethod: android.text.method.TransformationMethod;
private _paintFlags: number;
private _minHeight: number;
Expand Down Expand Up @@ -237,6 +237,9 @@ export class TextBase extends TextBaseCommon {

this._setNativeText(reset);
}
[textStrokeProperty.setNative](value: StrokeCSSValues) {
this._setNativeText();
}
createFormattedTextNative(value: FormattedString) {
return createSpannableStringBuilder(value, this.style.fontSize);
}
Expand Down Expand Up @@ -386,7 +389,7 @@ export class TextBase extends TextBaseCommon {
}
}

[textDecorationProperty.getDefault](value: number) {
[textDecorationProperty.getDefault]() {
return (this._paintFlags = this.nativeTextViewProtected.getPaintFlags());
}

Expand All @@ -410,7 +413,7 @@ export class TextBase extends TextBaseCommon {
}
}

[textShadowProperty.getDefault](value: number) {
[textShadowProperty.getDefault]() {
return {
radius: this.nativeTextViewProtected.getShadowRadius(),
offsetX: this.nativeTextViewProtected.getShadowDx(),
Expand Down Expand Up @@ -498,6 +501,13 @@ export class TextBase extends TextBaseCommon {
transformedText = getTransformedText(stringValue, this.textTransform);
}

if (this.style?.textStroke) {
this.nativeViewProtected.setTextStroke(Length.toDevicePixels(this.style.textStroke.width), this.style.textStroke.color.android, this.style.color.android);
} else if (this.nativeViewProtected.setTextStroke) {
// reset
this.nativeViewProtected.setTextStroke(0, 0, 0);
}

this.nativeTextViewProtected.setText(<any>transformedText);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/ui/text-base/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Length } from '../styling/style-properties';
import { Property, CssProperty, InheritedCssProperty } from '../core/properties';
import { CoreTypes } from '../../core-types';
import { ShadowCSSValues } from '../styling/css-shadow';
import { StrokeCSSValues } from '../styling/css-stroke';

export class TextBase extends View implements AddChildFromBuilder {
/**
Expand Down Expand Up @@ -138,6 +139,7 @@ export const textAlignmentProperty: InheritedCssProperty<Style, CoreTypes.TextAl
export const textDecorationProperty: CssProperty<Style, CoreTypes.TextDecorationType>;
export const textTransformProperty: CssProperty<Style, CoreTypes.TextTransformType>;
export const textShadowProperty: CssProperty<Style, ShadowCSSValues>;
export const textStrokeProperty: CssProperty<Style, StrokeCSSValues>;
export const whiteSpaceProperty: CssProperty<Style, CoreTypes.WhiteSpaceType>;
export const textOverflowProperty: CssProperty<Style, CoreTypes.TextOverflowType>;
export const letterSpacingProperty: CssProperty<Style, number>;
Expand Down
Loading