From 673387cf99fd2ed71ce2d3b1323954a0116a5045 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 19 Feb 2021 14:51:31 -0800 Subject: [PATCH] chore: cleanup --- apps/ui/src/css/text-shadow-page.ts | 2 +- packages/core/ui/core/view/view-common.ts | 6 +- packages/core/ui/styling/background.ios.ts | 4 +- .../styling/{box-shadow.ts => css-shadow.ts} | 2 +- packages/core/ui/styling/style-properties.ts | 48 +++++++------ packages/core/ui/styling/style/index.ts | 4 +- packages/core/ui/text-base/index.ios.ts | 70 +++++++++++++++---- .../core/ui/text-base/text-base-common.ts | 35 +++++++--- .../core/ui/text-base/text-base-interfaces.ts | 6 -- 9 files changed, 117 insertions(+), 60 deletions(-) rename packages/core/ui/styling/{box-shadow.ts => css-shadow.ts} (87%) diff --git a/apps/ui/src/css/text-shadow-page.ts b/apps/ui/src/css/text-shadow-page.ts index bb999aa15..b3cab3f05 100644 --- a/apps/ui/src/css/text-shadow-page.ts +++ b/apps/ui/src/css/text-shadow-page.ts @@ -1,6 +1,6 @@ import { EventData, TextBase } from '@nativescript/core'; -const possibleValues = ['2 10 4 rgb(255, 100, 100)', '2 10 2 rgba(10, 10, 10, 0.5)', '1 1 1 #55a', '2 2 2 #aaa', '']; +const possibleValues = ['2 10 4 rgb(255, 100, 100)', '2 10 2 rgba(10, 10, 10, 0.5)', '1 1 1 #55a', '2 2 2 #aaa', '0 0 1 yellow', '-1 -1 1 #aaa', '']; let currentIndex = 0; export function butonTap(args: EventData) { diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts index cff706363..9e8c13330 100644 --- a/packages/core/ui/core/view/view-common.ts +++ b/packages/core/ui/core/view/view-common.ts @@ -25,7 +25,7 @@ import * as am from '../../animation'; import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait, AndroidAccessibilityEvent, IOSPostAccessibilityNotificationType } from '../../../accessibility/accessibility-types'; import { accessibilityHintProperty, accessibilityIdentifierProperty, accessibilityLabelProperty, accessibilityTraitsProperty, accessibilityValueProperty } from '../../../accessibility/accessibility-properties'; import { accessibilityBlurEvent, accessibilityFocusChangedEvent, accessibilityFocusEvent, getCurrentFontScale } from '../../../accessibility'; -import { BoxShadow } from '../../styling/box-shadow'; +import { CSSShadow } from '../../styling/css-shadow'; // helpers (these are okay re-exported here) export * from './view-helper'; @@ -591,10 +591,10 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition { this.style.backgroundRepeat = value; } - get boxShadow(): BoxShadow { + get boxShadow(): CSSShadow { return this.style.boxShadow; } - set boxShadow(value: BoxShadow) { + set boxShadow(value: CSSShadow) { this.style.boxShadow = value; } diff --git a/packages/core/ui/styling/background.ios.ts b/packages/core/ui/styling/background.ios.ts index 9d0a52e45..b035ae656 100644 --- a/packages/core/ui/styling/background.ios.ts +++ b/packages/core/ui/styling/background.ios.ts @@ -7,7 +7,7 @@ import { Color } from '../../color'; import { isDataURI, isFileOrResourcePath, layout } from '../../utils'; import { ImageSource } from '../../image-source'; import { CSSValue, parse as cssParse } from '../../css-value'; -import { BoxShadow } from './box-shadow'; +import { CSSShadow } from './css-shadow'; export * from './background-common'; @@ -717,7 +717,7 @@ function drawNoRadiusNonUniformBorders(nativeView: NativeView, background: Backg } // TODO: use sublayer if its applied to a layout -function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: BoxShadow, background: BackgroundDefinition, useSubLayer: boolean = false) { +function drawBoxShadow(nativeView: NativeView, view: View, boxShadow: CSSShadow, background: BackgroundDefinition, useSubLayer: boolean = false) { const layer: CALayer = nativeView.layer; layer.masksToBounds = false; diff --git a/packages/core/ui/styling/box-shadow.ts b/packages/core/ui/styling/css-shadow.ts similarity index 87% rename from packages/core/ui/styling/box-shadow.ts rename to packages/core/ui/styling/css-shadow.ts index a2427a64f..2410474ed 100644 --- a/packages/core/ui/styling/box-shadow.ts +++ b/packages/core/ui/styling/css-shadow.ts @@ -1,6 +1,6 @@ import { Color } from '../../color'; -export class BoxShadow { +export class CSSShadow { public offsetX: number; public offsetY: number; public blurRadius: number; diff --git a/packages/core/ui/styling/style-properties.ts b/packages/core/ui/styling/style-properties.ts index ee30e2382..d40d68914 100644 --- a/packages/core/ui/styling/style-properties.ts +++ b/packages/core/ui/styling/style-properties.ts @@ -17,7 +17,7 @@ import { Trace } from '../../trace'; import * as parser from '../../css/parser'; import { LinearGradient } from './linear-gradient'; -import { BoxShadow } from './box-shadow'; +import { CSSShadow } from './css-shadow'; export type LengthDipUnit = { readonly unit: 'dip'; readonly value: dip }; export type LengthPxUnit = { readonly unit: 'px'; readonly value: px }; @@ -452,10 +452,10 @@ export const verticalAlignmentProperty = new CssProperty -1) { arr = value.split(' '); colorRaw = arr.pop(); @@ -464,27 +464,29 @@ function parseBoxShadowProperites(value: string): BoxShadow { 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 offsetX = 0; + let offsetY = 0; + let blurRadius = 0; // not currently in use + let spreadRadius = 0; // 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); + if (arr.length === 1) { + Trace.write('Expected 3, 4 or 5 parameters. Actual: ' + value, Trace.categories.Error, Trace.messageType.error); } + + if (arr.length > 1) { + offsetX = parseFloat(arr[0]); + offsetY = parseFloat(arr[1]); + } + + if (arr.length > 2) { + blurRadius = parseFloat(arr[2]); + } + + if (arr.length > 3) { + spreadRadius = parseFloat(arr[3]); + } + return { offsetX: offsetX, offsetY: offsetY, @@ -1321,14 +1323,14 @@ export const borderBottomLeftRadiusProperty = new CssProperty({ }); borderBottomLeftRadiusProperty.register(Style); -const boxShadowProperty = new CssProperty({ +const boxShadowProperty = new CssProperty({ name: 'boxShadow', cssName: 'box-shadow', valueChanged: (target, oldValue, newValue) => { target.boxShadow = newValue; }, valueConverter: (value) => { - return parseBoxShadowProperites(value); + return parseShadowProperites(value); }, }); boxShadowProperty.register(Style); diff --git a/packages/core/ui/styling/style/index.ts b/packages/core/ui/styling/style/index.ts index edfc77e6c..7d9d715e0 100644 --- a/packages/core/ui/styling/style/index.ts +++ b/packages/core/ui/styling/style/index.ts @@ -12,7 +12,7 @@ import { FlexDirection, FlexWrap, JustifyContent, AlignItems, AlignContent, Orde import { Trace } from '../../../trace'; import { TextAlignment, TextDecoration, TextTransform, WhiteSpace, TextShadow } from '../../text-base'; import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types'; -import { BoxShadow } from '../box-shadow'; +import { CSSShadow } from '../css-shadow'; export interface CommonLayoutParams { width: number; @@ -140,7 +140,7 @@ export class Style extends Observable implements StyleDefinition { public borderBottomRightRadius: Length; public borderBottomLeftRadius: Length; - public boxShadow: BoxShadow; + public boxShadow: CSSShadow; public fontSize: number; public fontFamily: string; diff --git a/packages/core/ui/text-base/index.ios.ts b/packages/core/ui/text-base/index.ios.ts index ef3c5aa2c..4e4543dbd 100644 --- a/packages/core/ui/text-base/index.ios.ts +++ b/packages/core/ui/text-base/index.ios.ts @@ -7,9 +7,10 @@ import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentPrope import { Color } from '../../color'; import { FormattedString } from './formatted-string'; import { Span } from './span'; -import { colorProperty, fontInternalProperty, VerticalAlignment } from '../styling/style-properties'; +import { colorProperty, fontInternalProperty, Length, VerticalAlignment } from '../styling/style-properties'; import { isString, isDefined, isNullOrUndefined } from '../../utils/types'; import { iOSNativeHelper } from '../../utils'; +import { Trace } from '../../trace'; export * from './text-base-common'; @@ -348,12 +349,10 @@ export class TextBase extends TextBaseCommon { } _setShadow(value: TextShadow): void { - let layer; - - if (this.nativeTextViewProtected instanceof UITextView) { - layer = this.nativeTextViewProtected.layer.sublayers.objectAtIndex(1); - } else { - layer = this.nativeTextViewProtected.layer; + const layer = getShadowLayer(this); + if (!layer) { + Trace.write('text-shadow not applied, no layer.', Trace.categories.Style, Trace.messageType.info); + return; } if (isNullOrUndefined(value)) { @@ -365,12 +364,24 @@ export class TextBase extends TextBaseCommon { return; } - layer.shadowOpacity = 1; - layer.shadowRadius = value.blurRadius; - layer.shadowColor = value.color.ios.CGColor; - layer.shadowOffset = CGSizeMake(value.offsetX, value.offsetY); - layer.shouldRasterize = true; + if (value.color) { + layer.shadowOpacity = value.color.a / 255; + layer.shadowColor = value.color.ios.CGColor; + } + + if (value.blurRadius) { + layer.shadowRadius = Length.toDevicePixels(value.blurRadius); + } + + layer.shadowOffset = CGSizeMake(Length.toDevicePixels(value.offsetX), Length.toDevicePixels(value.offsetY)); + // layer.shadowOffset = CGSizeMake(Length.toDevicePixels(value.offsetX), Length.toDevicePixels(value.offsetY)); layer.masksToBounds = false; + + // NOTE: generally should not need shouldRaterize + // however for various detailed animation work which involves text-shadow applicable layers, we may want to give users the control of enabling this with text-shadow + // if (!(this.nativeTextViewProtected instanceof UITextView)) { + // layer.shouldRasterize = true; + // } } createNSMutableAttributedString(formattedString: FormattedString): NSMutableAttributedString { @@ -493,6 +504,41 @@ export function getTransformedText(text: string, textTransform: TextTransform): } } +export function getShadowLayer(view: TextBase): CALayer { + let layer: CALayer; + const name = 'shadow-layer'; + const nativeView = view && view.nativeTextViewProtected; + if (nativeView) { + if (nativeView.layer) { + if (nativeView.layer.name === name) { + return nativeView.layer; + } else { + if (nativeView.layer.sublayers && nativeView.layer.sublayers.count) { + console.log('this.nativeTextViewProtected.layer.sublayers.count:', nativeView.layer.sublayers.count); + for (let i = 0; i < nativeView.layer.sublayers.count; i++) { + console.log(`layer ${i}:`, nativeView.layer.sublayers.objectAtIndex(i)); + if (nativeView.layer.sublayers.objectAtIndex(i).name === name) { + return nativeView.layer.sublayers.objectAtIndex(i); + } + } + if (nativeView instanceof UITextView) { + layer = nativeView.layer.sublayers.objectAtIndex(1); + } else { + layer = nativeView.layer.sublayers.objectAtIndex(nativeView.layer.sublayers.count - 1); + } + } else { + layer = nativeView.layer; + } + } + } else { + // could this occur? + console.log('no layer!'); + } + } + layer.name = name; + return layer; +} + function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString { return NSString.stringWithString((source instanceof NSAttributedString && source.string) || source); } diff --git a/packages/core/ui/text-base/text-base-common.ts b/packages/core/ui/text-base/text-base-common.ts index f6224d4c7..9755cc1b7 100644 --- a/packages/core/ui/text-base/text-base-common.ts +++ b/packages/core/ui/text-base/text-base-common.ts @@ -9,12 +9,13 @@ import { Span } from './span'; import { View } from '../core/view'; import { Property, CssProperty, InheritedCssProperty, makeValidator, makeParser } from '../core/properties'; import { Style } from '../styling/style'; -import { Length } from '../styling/style-properties'; +import { Length, parseShadowProperites } from '../styling/style-properties'; import { Observable } from '../../data/observable'; -import { TextAlignment, TextDecoration, TextTransform, WhiteSpace, TextShadow } from './text-base-interfaces'; +import { TextAlignment, TextDecoration, TextTransform, WhiteSpace } from './text-base-interfaces'; import { TextBase as TextBaseDefinition } from '.'; import { Color } from '../../color'; +import { CSSShadow } from '../styling/css-shadow'; export * from './text-base-interfaces'; @@ -27,6 +28,26 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition public text: string; public formattedText: FormattedString; + /*** + * In the NativeScript Core; by default the nativeTextViewProtected points to the same value as nativeViewProtected. + * At this point no internal NS components need this indirection functionality. + * This indirection is used to allow support usage by third party components so they don't have to duplicate functionality. + * + * A third party component can just override the `nativeTextViewProtected` getter and return a different internal view and that view would be + * what all TextView/TextInput class features would be applied to. + * + * A example is the Android MaterialDesign TextInput class, it has a wrapper view of a TextInputLayout + * https://developer.android.com/reference/com/google/android/material/textfield/TextInputLayout + * which wraps the actual TextInput. This wrapper layout (TextInputLayout) must be assigned to the nativeViewProtected as the entire + * NS Core uses nativeViewProtected for everything related to layout, so that it can be measured, added to the parent view as a child, ect. + * + * However, its internal view would be the actual TextView/TextInput and to allow that sub-view to have the normal TextView/TextInput + * class features, which we expose and to allow them to work on it, the internal TextView/TextInput is what the needs to have the class values applied to it. + * + * So all code that works on what is expected to be a TextView/TextInput should use `nativeTextViewProtected` so that any third party + * components that need to have two separate components can work properly without them having to duplicate all the TextBase (and decendants) functionality + * by just overriding the nativeTextViewProtected getter. + **/ get nativeTextViewProtected() { return this.nativeViewProtected; } @@ -247,18 +268,12 @@ export const textTransformProperty = new CssProperty({ }); textTransformProperty.register(Style); -export const textShadowProperty = new CssProperty({ +export const textShadowProperty = new CssProperty({ name: 'textShadow', cssName: 'text-shadow', affectsLayout: global.isIOS, valueConverter: (value) => { - const params = value.split(' '); - return { - offsetX: Length.parse(params[0]), - offsetY: Length.parse(params[1]), - blurRadius: Length.parse(params[2]), - color: new Color(params.slice(3).join('')), - }; + return parseShadowProperites(value); }, }); textShadowProperty.register(Style); diff --git a/packages/core/ui/text-base/text-base-interfaces.ts b/packages/core/ui/text-base/text-base-interfaces.ts index f1085371e..037ca86c8 100644 --- a/packages/core/ui/text-base/text-base-interfaces.ts +++ b/packages/core/ui/text-base/text-base-interfaces.ts @@ -5,9 +5,3 @@ export type WhiteSpace = 'initial' | 'normal' | 'nowrap'; export type TextAlignment = 'initial' | 'left' | 'center' | 'right'; export type TextTransform = 'initial' | 'none' | 'capitalize' | 'uppercase' | 'lowercase'; export type TextDecoration = 'none' | 'underline' | 'line-through' | 'underline line-through'; -export type TextShadow = { - offsetX: Length; - offsetY: Length; - blurRadius: Length; - color: Color; -};