mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-06 17:28:29 +08:00
feat(css): text-stroke support (#10399)
closes https://github.com/NativeScript/NativeScript/issues/3597 closes https://github.com/NativeScript/NativeScript/issues/3972
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { Page, Observable, EventData } from '@nativescript/core';
|
||||
import { Page, Observable, EventData, Label, Color } from '@nativescript/core';
|
||||
|
||||
let page: Page;
|
||||
|
||||
@ -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' },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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."
|
||||
|
Binary file not shown.
@ -10,4 +10,6 @@
|
||||
|
||||
-(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details letterSpacing:(CGFloat)letterSpacing lineHeight:(CGFloat)lineHeight;
|
||||
|
||||
-(void)nativeScriptSetFormattedTextStroke:(CGFloat)width color:(UIColor*)color;
|
||||
|
||||
@end
|
||||
|
@ -130,4 +130,19 @@
|
||||
((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
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
@ -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 {
|
||||
|
@ -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];
|
||||
@ -44,15 +40,15 @@ 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);
|
||||
@ -60,51 +56,30 @@ export function parseCSSShadow(value: string): ShadowCSSValues {
|
||||
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
packages/core/ui/styling/css-stroke.spec.ts
Normal file
30
packages/core/ui/styling/css-stroke.spec.ts
Normal file
@ -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
packages/core/ui/styling/css-stroke.ts
Normal file
23
packages/core/ui/styling/css-stroke.ts
Normal file
@ -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),
|
||||
};
|
||||
}
|
@ -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;
|
||||
@ -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;
|
||||
|
||||
|
@ -5,9 +5,10 @@ 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';
|
||||
@ -15,7 +16,6 @@ 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';
|
||||
@ -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;
|
||||
@ -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);
|
||||
}
|
||||
@ -386,7 +389,7 @@ export class TextBase extends TextBaseCommon {
|
||||
}
|
||||
}
|
||||
|
||||
[textDecorationProperty.getDefault](value: number) {
|
||||
[textDecorationProperty.getDefault]() {
|
||||
return (this._paintFlags = this.nativeTextViewProtected.getPaintFlags());
|
||||
}
|
||||
|
||||
@ -410,7 +413,7 @@ export class TextBase extends TextBaseCommon {
|
||||
}
|
||||
}
|
||||
|
||||
[textShadowProperty.getDefault](value: number) {
|
||||
[textShadowProperty.getDefault]() {
|
||||
return {
|
||||
radius: this.nativeTextViewProtected.getShadowRadius(),
|
||||
offsetX: this.nativeTextViewProtected.getShadowDx(),
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
2
packages/core/ui/text-base/index.d.ts
vendored
2
packages/core/ui/text-base/index.d.ts
vendored
@ -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 {
|
||||
/**
|
||||
@ -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>;
|
||||
|
@ -5,11 +5,12 @@ import { ShadowCSSValues } from '../styling/css-shadow';
|
||||
// Requires
|
||||
import { Font } from '../styling/font';
|
||||
import { iosAccessibilityAdjustsFontSizeProperty, iosAccessibilityMaxFontScaleProperty, iosAccessibilityMinFontScaleProperty } from '../../accessibility/accessibility-properties';
|
||||
import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
|
||||
import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
|
||||
import { Color } from '../../color';
|
||||
import { FormattedString } from './formatted-string';
|
||||
import { Span } from './span';
|
||||
import { colorProperty, fontInternalProperty, fontScaleInternalProperty, Length } from '../styling/style-properties';
|
||||
import { StrokeCSSValues } from '../styling/css-stroke';
|
||||
import { isString, isNullOrUndefined } from '../../utils/types';
|
||||
import { iOSNativeHelper, layout } from '../../utils';
|
||||
import { Trace } from '../../trace';
|
||||
@ -247,6 +248,10 @@ export class TextBase extends TextBaseCommon {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[textStrokeProperty.setNative](value: StrokeCSSValues) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[letterSpacingProperty.setNative](value: number) {
|
||||
this._setNativeText();
|
||||
}
|
||||
@ -306,16 +311,19 @@ export class TextBase extends TextBaseCommon {
|
||||
const letterSpacing = this.style.letterSpacing ? this.style.letterSpacing : 0;
|
||||
const lineHeight = this.style.lineHeight ? this.style.lineHeight : 0;
|
||||
if (this.formattedText) {
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText), letterSpacing, lineHeight);
|
||||
this.nativeTextViewProtected.nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText) as any, letterSpacing, lineHeight);
|
||||
} else {
|
||||
// console.log('setTextDecorationAndTransform...')
|
||||
const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
|
||||
this.nativeTextViewProtected.nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
|
||||
|
||||
if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) {
|
||||
this._setColor(UIColor.labelColor);
|
||||
}
|
||||
}
|
||||
if (this.style?.textStroke) {
|
||||
this.nativeTextViewProtected.nativeScriptSetFormattedTextStrokeColor(Length.toDevicePixels(this.style.textStroke.width, 0), this.style.textStroke.color.ios);
|
||||
}
|
||||
}
|
||||
|
||||
createFormattedTextNative(value: FormattedString) {
|
||||
|
@ -14,6 +14,7 @@ import { CoreTypes } from '../../core-types';
|
||||
import { TextBase as TextBaseDefinition } from '.';
|
||||
import { Color } from '../../color';
|
||||
import { ShadowCSSValues, parseCSSShadow } from '../styling/css-shadow';
|
||||
import { StrokeCSSValues, parseCSSStroke } from '../styling/css-stroke';
|
||||
|
||||
const CHILD_SPAN = 'Span';
|
||||
const CHILD_FORMATTED_TEXT = 'formattedText';
|
||||
@ -288,6 +289,16 @@ export const textShadowProperty = new CssProperty<Style, string | ShadowCSSValue
|
||||
});
|
||||
textShadowProperty.register(Style);
|
||||
|
||||
export const textStrokeProperty = new CssProperty<Style, string | StrokeCSSValues>({
|
||||
name: 'textStroke',
|
||||
cssName: 'text-stroke',
|
||||
affectsLayout: global.isIOS,
|
||||
valueConverter: (value) => {
|
||||
return parseCSSStroke(value);
|
||||
},
|
||||
});
|
||||
textStrokeProperty.register(Style);
|
||||
|
||||
const whiteSpaceConverter = makeParser<CoreTypes.WhiteSpaceType>(makeValidator<CoreTypes.WhiteSpaceType>('initial', 'normal', 'nowrap'));
|
||||
export const whiteSpaceProperty = new CssProperty<Style, CoreTypes.WhiteSpaceType>({
|
||||
name: 'whiteSpace',
|
||||
|
@ -403,6 +403,13 @@
|
||||
setImageLoadedListener(listener: image.Worker.OnImageLoadedListener): void;
|
||||
}
|
||||
|
||||
export class StyleableTextView extends android.widget.TextView {
|
||||
public static class: java.lang.Class<org.nativescript.widgets.StyleableTextView>;
|
||||
public onDraw(param0: globalAndroid.graphics.Canvas): void;
|
||||
public setTextStroke(param0: number, param1: number, param2: number): void;
|
||||
public constructor(param0: globalAndroid.content.Context);
|
||||
}
|
||||
|
||||
export enum TabIconRenderingMode {
|
||||
original,
|
||||
template
|
||||
|
@ -25831,8 +25831,11 @@ declare class UIView extends UIResponder implements CALayerDelegate, NSCoding, U
|
||||
layoutSubviews(): void;
|
||||
|
||||
nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(details: NSDictionary<any, any>, letterSpacing: number, lineHeight: number): void;
|
||||
|
||||
|
||||
nativeScriptSetFormattedTextStrokeColor(width: number, color: UIColor): void;
|
||||
|
||||
nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text: string, textDecoration: string, letterSpacing: number, lineHeight: number): void;
|
||||
|
||||
|
||||
needsUpdateConstraints(): boolean;
|
||||
|
||||
|
@ -0,0 +1,50 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.TextPaint;
|
||||
|
||||
/**
|
||||
* @author NathanWalker
|
||||
*/
|
||||
public class StyleableTextView extends androidx.appcompat.widget.AppCompatTextView {
|
||||
int mTextStrokeWidth = 0;
|
||||
int mTextStrokeColor = 0;
|
||||
int mTextColor = 0;
|
||||
|
||||
public StyleableTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mTextStrokeWidth > 0) {
|
||||
_applyStroke(canvas);
|
||||
} else {
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextStroke(int width, int color, int textColor) {
|
||||
mTextStrokeWidth = width;
|
||||
mTextStrokeColor = color;
|
||||
mTextColor = textColor;
|
||||
}
|
||||
|
||||
private void _applyStroke(Canvas canvas) {
|
||||
// set paint to fill mode
|
||||
TextPaint p = this.getPaint();
|
||||
p.setStyle(TextPaint.Style.FILL);
|
||||
// draw the fill part of text
|
||||
super.onDraw(canvas);
|
||||
// stroke color and width
|
||||
p.setStyle(TextPaint.Style.STROKE);
|
||||
p.setStrokeWidth(mTextStrokeWidth);
|
||||
this.setTextColor(mTextStrokeColor);
|
||||
// draw stroke
|
||||
super.onDraw(canvas);
|
||||
// draw original text color fill back (fallback to white)
|
||||
p.setStyle(TextPaint.Style.FILL);
|
||||
this.setTextColor(mTextColor != 0 ? mTextColor : 0xffffffff);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user