diff --git a/apps/ui/src/issues/issue-7469-page.ts b/apps/ui/src/issues/issue-7469-page.ts index eca14bb6a4..764397a83e 100644 --- a/apps/ui/src/issues/issue-7469-page.ts +++ b/apps/ui/src/issues/issue-7469-page.ts @@ -28,7 +28,7 @@ let loaded = false; let isIn1 = false; function updateVcToggleText() { - vcToggle.text = `Container is${reusableItem.reusable ? ' ' : ' NOT '}Reusable` + vcToggle.text = `Container is${reusableItem.reusable ? ' ' : ' NOT '}Reusable`; } export function pageLoaded(args) { @@ -82,7 +82,7 @@ export function makeReusable(args: EventData) { } (args.object as any).___reusableRan = true; (args.object as any).reusable = true; - if(args.object === reusableItem) { + if (args.object === reusableItem) { updateVcToggleText(); } } diff --git a/packages/core/ui/layouts/layout-base.android.ts b/packages/core/ui/layouts/layout-base.android.ts index 9fa969fe86..7edfcd9298 100644 --- a/packages/core/ui/layouts/layout-base.android.ts +++ b/packages/core/ui/layouts/layout-base.android.ts @@ -9,18 +9,8 @@ export class LayoutBase extends LayoutBaseCommon { return true; } [clipToBoundsProperty.setNative](value: boolean) { - // TODO: Use ClipRectangle if API > 16! - - // We can't implement this without calling setClipChildren(false) on every ancestor up in the visual tree, - // which will kill performance. It will also lead to unwanted side effects such as other totally unrelated - // views being affected by setting the parents' setClipChildren to false. - // The problem in Android is that a ViewGroup either clips ALL of its children or it does not. Unlike iOS, the clipping - // cannot be controlled on a per view basis. So clipToBounds=false will have to be somehow achieved with stacking different - // views on top of one another in an AbsoluteLayout or GridLayout. There is always a workaround when playing with layouts. - // - // The following article explains this in detail: - // http://stackoverflow.com/questions/25044085/when-drawing-outside-the-view-clip-bounds-with-android-how-do-i-prevent-underli - console.warn(`clipToBounds with value false is not supported on Android. You can use this.android.getParent().setClipChildren(false) as an alternative`); + (this.nativeViewProtected).setClipToBounds(value); + } [isPassThroughParentEnabledProperty.setNative](value: boolean) { diff --git a/packages/core/ui/text-base/index.android.ts b/packages/core/ui/text-base/index.android.ts index 64fa7e8d4e..edbee315f7 100644 --- a/packages/core/ui/text-base/index.android.ts +++ b/packages/core/ui/text-base/index.android.ts @@ -437,7 +437,7 @@ export class TextBase extends TextBaseCommon { [paddingLeftProperty.setNative](value: CoreTypes.LengthType) { org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0)); } - + [accessibilityIdentifierProperty.setNative](value: string): void { // we override the default setter to apply it on nativeTextViewProtected const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id'); diff --git a/packages/ui-mobile-base/android/widgets/build.gradle b/packages/ui-mobile-base/android/widgets/build.gradle index a8b2f37b64..20f8ff6cd3 100644 --- a/packages/ui-mobile-base/android/widgets/build.gradle +++ b/packages/ui-mobile-base/android/widgets/build.gradle @@ -51,7 +51,12 @@ def computeTargetSdkVersion() { } } + android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } compileSdkVersion computeCompileSdkVersion() buildToolsVersion computeBuildToolsVersion() diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java index 2f7853a5aa..c6dda3e15d 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/BorderDrawable.java @@ -18,13 +18,20 @@ import android.graphics.drawable.Drawable; import android.graphics.Shader; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import org.nativescript.widgets.image.BitmapOwner; import org.nativescript.widgets.image.Fetcher; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.Locale; import java.util.regex.Pattern; +import android.os.Build; +import android.util.Log; + /** * Created by hristov on 6/15/2016. */ @@ -49,6 +56,20 @@ public class BorderDrawable extends ColorDrawable implements BitmapOwner { private String clipPath; + private Path clipPathPath = null; + private Path backgroundPath = null; + private Path backgroundOutlinePath = null; + private Path unifiedColorBorderPath = null; + private Path innerBorderPath = null; + private Path topBorderPath = null; + private Path rightBorderPath = null; + private Path bottomBorderPath = null; + private Path leftBorderPath = null; + private Path clippingPath = null; + private Rect lastBounds = null; + + Paint borderPaint = null; + private int backgroundColor; private String backgroundImage; private Bitmap backgroundBitmap; @@ -141,6 +162,65 @@ public String getClipPath() { return clipPath; } + public Path getClipPathPath() { + return clipPathPath; + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public Path getClippingPath() { + Path toClip = null; + if (this.clipPath != null) { + generateClipPath(this.clipPath, new RectF(getBounds()), density); + toClip = this.clipPathPath; + } else if (hasBorderWidth()) { + generateInnerBorderPath(getBounds()); + toClip = this.innerBorderPath; + } else if (android.os.Build.VERSION.SDK_INT < 21 || !this.hasUniformBorder()) { + generateBackgroundOutlinePath(getBounds()); + toClip = this.backgroundOutlinePath; + } + if (toClip != null) { + if (clippingPath != null && hasCache("clippingPath")) { + return clippingPath; + } + setHasCache("clippingPath"); + + if (clippingPath == null) { + clippingPath = new Path(); + } else { + clippingPath.reset(); + } + clippingPath.addRect(new RectF(lastBounds), Path.Direction.CW); + clippingPath.op(toClip, Path.Op.DIFFERENCE); + return clippingPath; + } + // if uniform borders and no border width and >= 21 then the oultine can clip + // no need to return a clippingPath + return null; + } + + HashSet cacheKeys = new HashSet(); + + private boolean hasCache(String key) { + return (cacheKeys.contains(key)); + } + + private void setHasCache(String key) { + cacheKeys.add(key); + } + + protected void onBoundsChange(Rect bounds) { + if (lastBounds != null && lastBounds.equals(bounds)) { + return; + } + + if (lastBounds == null) { + lastBounds = new Rect(); + } + lastBounds.set(bounds); + cacheKeys.clear(); + } + public int getBackgroundColor() { return backgroundColor; } @@ -153,7 +233,9 @@ public Bitmap getBackgroundBitmap() { return backgroundBitmap; } - public LinearGradientDefinition getBackgroundGradient() { return backgroundGradient; } + public LinearGradientDefinition getBackgroundGradient() { + return backgroundGradient; + } public String getBackgroundRepeat() { return backgroundRepeat; @@ -168,34 +250,33 @@ public String getBackgroundSize() { } public boolean hasBorderWidth() { - return this.borderTopWidth != 0 - || this.borderRightWidth != 0 - || this.borderBottomWidth != 0 - || this.borderLeftWidth != 0; + return this.borderTopWidth != 0 || this.borderRightWidth != 0 || this.borderBottomWidth != 0 + || this.borderLeftWidth != 0; } public boolean hasUniformBorderColor() { - return this.borderTopColor == this.borderRightColor && - this.borderTopColor == this.borderBottomColor && - this.borderTopColor == this.borderLeftColor; + return this.borderTopColor == this.borderRightColor && this.borderTopColor == this.borderBottomColor + && this.borderTopColor == this.borderLeftColor; } public boolean hasUniformBorderWidth() { - return this.borderTopWidth == this.borderRightWidth && - this.borderTopWidth == this.borderBottomWidth && - this.borderTopWidth == this.borderLeftWidth; + return this.borderTopWidth == this.borderRightWidth && this.borderTopWidth == this.borderBottomWidth + && this.borderTopWidth == this.borderLeftWidth; } public boolean hasUniformBorderRadius() { - return this.borderTopLeftRadius == this.borderTopRightRadius && - this.borderTopLeftRadius == this.borderBottomRightRadius && - this.borderTopLeftRadius == this.borderBottomLeftRadius; + return this.borderTopLeftRadius == this.borderTopRightRadius + && this.borderTopLeftRadius == this.borderBottomRightRadius + && this.borderTopLeftRadius == this.borderBottomLeftRadius; + } + + public boolean hasBorderRadius() { + return borderBottomLeftRadius > 0 || borderTopLeftRadius > 0 || borderBottomRightRadius > 0 + || borderTopRightRadius > 0; } public boolean hasUniformBorder() { - return this.hasUniformBorderColor() && - this.hasUniformBorderWidth() && - this.hasUniformBorderRadius(); + return this.hasUniformBorderColor() && this.hasUniformBorderWidth() && this.hasUniformBorderRadius(); } public BorderDrawable(float density) { @@ -209,33 +290,19 @@ public BorderDrawable(float density, String id) { this.id = id; } - public void refresh(int borderTopColor, - int borderRightColor, - int borderBottomColor, - int borderLeftColor, + public void refresh(int borderTopColor, int borderRightColor, int borderBottomColor, int borderLeftColor, - float borderTopWidth, - float borderRightWidth, - float borderBottomWidth, - float borderLeftWidth, + float borderTopWidth, float borderRightWidth, float borderBottomWidth, float borderLeftWidth, - float borderTopLeftRadius, - float borderTopRightRadius, - float borderBottomRightRadius, - float borderBottomLeftRadius, + float borderTopLeftRadius, float borderTopRightRadius, float borderBottomRightRadius, + float borderBottomLeftRadius, - String clipPath, + String clipPath, - int backgroundColor, - String backgroundImageUri, - Bitmap backgroundBitmap, - LinearGradientDefinition backgroundGradient, - Context context, - String backgroundRepeat, - String backgroundPosition, - CSSValue[] backgroundPositionParsedCSSValues, - String backgroundSize, - CSSValue[] backgroundSizeParsedCSSValues) { + int backgroundColor, String backgroundImageUri, Bitmap backgroundBitmap, + LinearGradientDefinition backgroundGradient, Context context, String backgroundRepeat, + String backgroundPosition, CSSValue[] backgroundPositionParsedCSSValues, String backgroundSize, + CSSValue[] backgroundSizeParsedCSSValues) { this.borderTopColor = borderTopColor; this.borderRightColor = borderRightColor; @@ -254,6 +321,9 @@ public void refresh(int borderTopColor, this.clipPath = clipPath; + // clear all cached paths + cacheKeys.clear(); + this.backgroundColor = backgroundColor; this.backgroundImage = backgroundImageUri; this.backgroundBitmap = backgroundBitmap; @@ -273,39 +343,67 @@ public void refresh(int borderTopColor, } } - @Override - public void draw(Canvas canvas) { - Rect bounds = this.getBounds(); - float width = (float)bounds.width(); - float height = (float)bounds.height(); + private void generateBackgroundPath(Rect bounds) { - if (width <= 0 || height <= 0) { - // When the view is off-screen the bounds might be empty and we don't have anything to draw. + if (backgroundPath != null && hasCache("backgroundPath")) { return; } + setHasCache("backgroundPath"); - RectF backgroundBoundsF = new RectF(bounds.left, bounds.top, bounds.right, bounds.bottom); - + if (backgroundPath == null) { + backgroundPath = new Path(); + } else { + backgroundPath.reset(); + } + float[] backgroundRadii = { Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), + Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), + Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), + Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) }; float topBackoffAntialias = calculateBackoffAntialias(this.borderTopColor, this.borderTopWidth); float rightBackoffAntialias = calculateBackoffAntialias(this.borderRightColor, this.borderRightWidth); float bottomBackoffAntialias = calculateBackoffAntialias(this.borderBottomColor, this.borderBottomWidth); float leftBackoffAntialias = calculateBackoffAntialias(this.borderLeftColor, this.borderLeftWidth); - float[] backgroundRadii = { - Math.max(0, borderTopLeftRadius + leftBackoffAntialias), Math.max(0, borderTopLeftRadius + topBackoffAntialias), - Math.max(0, borderTopRightRadius + rightBackoffAntialias), Math.max(0, borderTopRightRadius + topBackoffAntialias), - Math.max(0, borderBottomRightRadius + rightBackoffAntialias), Math.max(0, borderBottomRightRadius + bottomBackoffAntialias), - Math.max(0, borderBottomLeftRadius + leftBackoffAntialias), Math.max(0, borderBottomLeftRadius + bottomBackoffAntialias) - }; - - Path backgroundPath = new Path(); - RectF backgroundRect = new RectF( - leftBackoffAntialias, - topBackoffAntialias, - width - rightBackoffAntialias, - height - bottomBackoffAntialias - ); + float width = (float) bounds.width(); + float height = (float) bounds.height(); + + RectF backgroundRect = new RectF(leftBackoffAntialias, topBackoffAntialias, width - rightBackoffAntialias, + height - bottomBackoffAntialias); backgroundPath.addRoundRect(backgroundRect, backgroundRadii, Path.Direction.CW); + } + + private void generateUniformedColorBorderPath(Rect bounds) { + if (unifiedColorBorderPath != null && hasCache("unifiedColorBorderPath")) { + return; + } + setHasCache("unifiedColorBorderPath"); + + if (unifiedColorBorderPath == null) { + unifiedColorBorderPath = new Path(); + } else { + unifiedColorBorderPath.reset(); + } + // the path used for outer border is the same as outline + generateBackgroundOutlinePath(bounds); + unifiedColorBorderPath.addPath(backgroundOutlinePath); + + generateInnerBorderPath(bounds); + unifiedColorBorderPath.addPath(innerBorderPath); + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = this.getBounds(); + float width = (float) bounds.width(); + float height = (float) bounds.height(); + + if (width <= 0 || height <= 0) { + // When the view is off-screen the bounds might be empty and we don't have + // anything to draw. + return; + } + + RectF backgroundBoundsF = new RectF(lastBounds); // draw background if (this.backgroundColor != 0) { @@ -317,6 +415,7 @@ public void draw(Canvas canvas) { if (this.clipPath != null && !this.clipPath.isEmpty()) { drawClipPath(this.clipPath, canvas, backgroundColorPaint, backgroundBoundsF, this.density); } else { + generateBackgroundPath(bounds); canvas.drawPath(backgroundPath, backgroundColorPaint); } } @@ -335,11 +434,9 @@ public void draw(Canvas canvas) { transform.postTranslate(params.posX, params.posY); Paint backgroundImagePaint = new Paint(); - BitmapShader shader = new BitmapShader( - this.backgroundBitmap, - params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP, - params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP - ); + BitmapShader shader = new BitmapShader(this.backgroundBitmap, + params.repeatX ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP, + params.repeatY ? Shader.TileMode.REPEAT : Shader.TileMode.CLAMP); shader.setLocalMatrix(transform); backgroundImagePaint.setAntiAlias(true); backgroundImagePaint.setFilterBitmap(true); @@ -354,13 +451,16 @@ public void draw(Canvas canvas) { drawClipPath(this.clipPath, canvas, backgroundImagePaint, backgroundBoundsF, this.density); } else { boolean supportsPathOp = android.os.Build.VERSION.SDK_INT >= 19; + generateBackgroundPath(bounds); if (supportsPathOp) { Path backgroundNoRepeatPath = new Path(); - backgroundNoRepeatPath.addRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight, Path.Direction.CCW); + backgroundNoRepeatPath.addRect(params.posX, params.posY, params.posX + imageWidth, + params.posY + imageHeight, Path.Direction.CCW); intersect(backgroundNoRepeatPath, backgroundPath); canvas.drawPath(backgroundNoRepeatPath, backgroundImagePaint); } else { - // Clipping here will not be anti-aliased but at least it won't shine through the rounded corners. + // Clipping here will not be anti-aliased but at least it won't shine through + // the rounded corners. canvas.save(); canvas.clipRect(params.posX, params.posY, params.posX + imageWidth, params.posY + imageHeight); canvas.drawPath(backgroundPath, backgroundImagePaint); @@ -372,10 +472,9 @@ public void draw(Canvas canvas) { if (this.backgroundGradient != null) { LinearGradientDefinition def = this.backgroundGradient; Paint backgroundGradientPaint = new Paint(); - LinearGradient shader = new LinearGradient( - def.getStartX() * width, def.getStartY() * height, - def.getEndX() * width, def.getEndY() * height, - def.getColors(), def.getStops(), Shader.TileMode.MIRROR); + LinearGradient shader = new LinearGradient(def.getStartX() * width, def.getStartY() * height, + def.getEndX() * width, def.getEndY() * height, def.getColors(), def.getStops(), + Shader.TileMode.MIRROR); backgroundGradientPaint.setAntiAlias(true); backgroundGradientPaint.setFilterBitmap(true); backgroundGradientPaint.setShader(shader); @@ -391,7 +490,10 @@ public void draw(Canvas canvas) { if (this.clipPath != null && !this.clipPath.isEmpty()) { float borderWidth = this.getUniformBorderWidth(); if (borderWidth > 0) { - Paint borderPaint = new Paint(); + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } borderPaint.setColor(this.getUniformBorderColor()); borderPaint.setStyle(Paint.Style.STROKE); borderPaint.setStrokeWidth(borderWidth); @@ -402,36 +504,15 @@ public void draw(Canvas canvas) { } else if (this.hasUniformBorderColor()) { // iOS and browsers use black when no color is specified. if (borderLeftWidth > 0 || borderTopWidth > 0 || borderRightWidth > 0 || borderBottomWidth > 0) { - Paint borderPaint = new Paint(); - borderPaint.setColor(this.getUniformBorderColor()); + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } borderPaint.setStyle(Paint.Style.FILL); - borderPaint.setAntiAlias(true); - Path borderPath = new Path(); - - RectF borderOuterRect = new RectF(0, 0, width, height); - float[] borderOuterRadii = { - borderTopLeftRadius, borderTopLeftRadius, - borderTopRightRadius, borderTopRightRadius, - borderBottomRightRadius, borderBottomRightRadius, - borderBottomLeftRadius, borderBottomLeftRadius - }; - borderPath.addRoundRect(borderOuterRect, borderOuterRadii, Path.Direction.CW); - - RectF borderInnerRect = new RectF( - borderLeftWidth, - borderTopWidth, - width - borderRightWidth, - height - borderBottomWidth - ); - float[] borderInnerRadii = { - Math.max(0, borderTopLeftRadius - borderLeftWidth), Math.max(0, borderTopLeftRadius - borderTopWidth), - Math.max(0, borderTopRightRadius - borderRightWidth), Math.max(0, borderTopRightRadius - borderTopWidth), - Math.max(0, borderBottomRightRadius - borderRightWidth), Math.max(0, borderBottomRightRadius - borderBottomWidth), - Math.max(0, borderBottomLeftRadius - borderLeftWidth), Math.max(0, borderBottomLeftRadius - borderBottomWidth) - }; - borderPath.addRoundRect(borderInnerRect, borderInnerRadii, Path.Direction.CCW); - - canvas.drawPath(borderPath, borderPaint); + borderPaint.setColor(this.getUniformBorderColor()); + + generateUniformedColorBorderPath(bounds); + canvas.drawPath(unifiedColorBorderPath, borderPaint); } } else { float top = this.borderTopWidth; @@ -439,16 +520,16 @@ public void draw(Canvas canvas) { float bottom = this.borderBottomWidth; float left = this.borderLeftWidth; - //lto rto - // +---------------------+ - // |lti rti| - // | | - // | | - // | | - // | | - // |lbi rbi| - // +---------------------+ - //lbo rbo + // lto rto + // +---------------------+ + // |lti rti| + // | | + // | | + // | | + // | | + // |lbi rbi| + // +---------------------+ + // lbo rbo PointF lto = new PointF(0, 0); // left-top-outside PointF lti = new PointF(left, top); // left-top-inside @@ -463,66 +544,109 @@ public void draw(Canvas canvas) { PointF lbi = new PointF(left, bounds.bottom - bottom); // left-bottom-inside if (this.borderTopWidth > 0) { - Paint topBorderPaint = new Paint(); - topBorderPaint.setColor(this.borderTopColor); - topBorderPaint.setAntiAlias(true); - Path topBorderPath = new Path(); - topBorderPath.setFillType(Path.FillType.EVEN_ODD); - topBorderPath.moveTo(lto.x, lto.y); - topBorderPath.lineTo(rto.x, rto.y); - topBorderPath.lineTo(rti.x, rti.y); - topBorderPath.lineTo(lti.x, lti.y); - topBorderPath.close(); - canvas.drawPath(topBorderPath, topBorderPaint); + + if (topBorderPath == null || !hasCache("topBorderPath")) { + setHasCache("topBorderPath"); + if (topBorderPath == null) { + topBorderPath = new Path(); + } else { + topBorderPath.reset(); + } + topBorderPath.setFillType(Path.FillType.EVEN_ODD); + topBorderPath.moveTo(lto.x, lto.y); + topBorderPath.lineTo(rto.x, rto.y); + topBorderPath.lineTo(rti.x, rti.y); + topBorderPath.lineTo(lti.x, lti.y); + topBorderPath.close(); + } + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } + borderPaint.setColor(this.borderTopColor); + canvas.drawPath(topBorderPath, borderPaint); } if (this.borderRightWidth > 0) { - Paint rightBorderPaint = new Paint(); - rightBorderPaint.setColor(this.borderRightColor); - rightBorderPaint.setAntiAlias(true); - Path rightBorderPath = new Path(); - rightBorderPath.setFillType(Path.FillType.EVEN_ODD); - rightBorderPath.moveTo(rto.x, rto.y); - rightBorderPath.lineTo(rbo.x, rbo.y); - rightBorderPath.lineTo(rbi.x, rbi.y); - rightBorderPath.lineTo(rti.x, rti.y); - rightBorderPath.close(); - canvas.drawPath(rightBorderPath, rightBorderPaint); + if (rightBorderPath == null || !hasCache("rightBorderPath")) { + setHasCache("rightBorderPath"); + if (rightBorderPath == null) { + rightBorderPath = new Path(); + } else { + rightBorderPath.reset(); + } + rightBorderPath.setFillType(Path.FillType.EVEN_ODD); + rightBorderPath.moveTo(rto.x, rto.y); + rightBorderPath.lineTo(rbo.x, rbo.y); + rightBorderPath.lineTo(rbi.x, rbi.y); + rightBorderPath.lineTo(rti.x, rti.y); + rightBorderPath.close(); + } + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } + + borderPaint.setColor(this.borderRightColor); + canvas.drawPath(rightBorderPath, borderPaint); } if (this.borderBottomWidth > 0) { - Paint bottomBorderPaint = new Paint(); - bottomBorderPaint.setColor(this.borderBottomColor); - bottomBorderPaint.setAntiAlias(true); - Path bottomBorderPath = new Path(); - bottomBorderPath.setFillType(Path.FillType.EVEN_ODD); - bottomBorderPath.moveTo(rbo.x, rbo.y); - bottomBorderPath.lineTo(lbo.x, lbo.y); - bottomBorderPath.lineTo(lbi.x, lbi.y); - bottomBorderPath.lineTo(rbi.x, rbi.y); - bottomBorderPath.close(); - canvas.drawPath(bottomBorderPath, bottomBorderPaint); + if (bottomBorderPath == null || !hasCache("bottomBorderPath")) { + setHasCache("bottomBorderPath"); + if (bottomBorderPath == null) { + bottomBorderPath = new Path(); + } else { + bottomBorderPath.reset(); + } + bottomBorderPath.setFillType(Path.FillType.EVEN_ODD); + bottomBorderPath.moveTo(rbo.x, rbo.y); + bottomBorderPath.lineTo(lbo.x, lbo.y); + bottomBorderPath.lineTo(lbi.x, lbi.y); + bottomBorderPath.lineTo(rbi.x, rbi.y); + bottomBorderPath.close(); + } + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } + + borderPaint.setColor(this.borderBottomColor); + canvas.drawPath(bottomBorderPath, borderPaint); } if (this.borderLeftWidth > 0) { - Paint leftBorderPaint = new Paint(); - leftBorderPaint.setColor(this.borderLeftColor); - leftBorderPaint.setAntiAlias(true); - Path leftBorderPath = new Path(); - leftBorderPath.setFillType(Path.FillType.EVEN_ODD); - leftBorderPath.moveTo(lbo.x, lbo.y); - leftBorderPath.lineTo(lto.x, lto.y); - leftBorderPath.lineTo(lti.x, lti.y); - leftBorderPath.lineTo(lbi.x, lbi.y); - leftBorderPath.close(); - canvas.drawPath(leftBorderPath, leftBorderPaint); + + if (leftBorderPath == null || !hasCache("leftBorderPath")) { + setHasCache("leftBorderPath"); + if (leftBorderPath == null) { + leftBorderPath = new Path(); + } else { + leftBorderPath.reset(); + } + leftBorderPath.setFillType(Path.FillType.EVEN_ODD); + leftBorderPath.moveTo(lbo.x, lbo.y); + leftBorderPath.lineTo(lto.x, lto.y); + leftBorderPath.lineTo(lti.x, lti.y); + leftBorderPath.lineTo(lbi.x, lbi.y); + leftBorderPath.close(); + } + if (borderPaint == null) { + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + } + + borderPaint.setColor(this.borderLeftColor); + canvas.drawPath(leftBorderPath, borderPaint); } } } private static float calculateBackoffAntialias(int borderColor, float borderWidth) { - // We will inset background colors and images so antialiasing will not color pixels outside the border. - // If the border is transparent we will backoff less, and we will not backoff more than half a pixel or half the border width. + // We will inset background colors and images so antialiasing will not color + // pixels outside the border. + // If the border is transparent we will backoff less, and we will not backoff + // more than half a pixel or half the border width. float halfBorderWidth = borderWidth / 2.0f; float normalizedBorderAlpha = ((float) Color.alpha(borderColor)) / 255.0f; return Math.min(1f, halfBorderWidth) * normalizedBorderAlpha; @@ -536,11 +660,22 @@ private static void intersect(Path path1, Path path2) { private static Pattern spaceAndComma = Pattern.compile("[\\s,]+"); private static Pattern space = Pattern.compile("\\s+"); - private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, RectF bounds, float density) { - // Sample string is polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%); + private void generateClipPath(String clipPath, RectF bounds, float density) { + if (clipPathPath != null && hasCache("clipPathPath")) { + return; + } + setHasCache("clipPathPath"); + + // Sample string is polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, + // 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%); String functionName = clipPath.substring(0, clipPath.indexOf("(")); String value = clipPath.substring(clipPath.indexOf("(") + 1, clipPath.indexOf(")")); + if (clipPathPath == null) { + clipPathPath = new Path(); + } else { + clipPathPath.reset(); + } String[] arr; float top; float right; @@ -554,8 +689,7 @@ private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, Re right = cssValueToDevicePixels(arr[1], bounds.right, density); bottom = cssValueToDevicePixels(arr[2], bounds.bottom, density); left = cssValueToDevicePixels(arr[3], bounds.right, density); - - canvas.drawRect(left, top, right, bottom, paint); + clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW); break; case "inset": arr = spaceAndComma.split(value); @@ -580,18 +714,21 @@ private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, Re } top = cssValueToDevicePixels(topString, bounds.bottom, density); - right = cssValueToDevicePixels("100%", bounds.right, density) - cssValueToDevicePixels(rightString, bounds.right, density); - bottom = cssValueToDevicePixels("100%", bounds.bottom, density) - cssValueToDevicePixels(bottomString, bounds.bottom, density); + right = cssValueToDevicePixels("100%", bounds.right, density) + - cssValueToDevicePixels(rightString, bounds.right, density); + bottom = cssValueToDevicePixels("100%", bounds.bottom, density) + - cssValueToDevicePixels(bottomString, bounds.bottom, density); left = cssValueToDevicePixels(leftString, bounds.right, density); - canvas.drawRect(left, top, right, bottom, paint); + clipPathPath.addRect(new RectF(left, top, right, bottom), Path.Direction.CW); break; case "circle": arr = space.split(value); - float radius = cssValueToDevicePixels(arr[0], (bounds.width() > bounds.height() ? bounds.height() : bounds.width()) / 2, density); + float radius = cssValueToDevicePixels(arr[0], + (bounds.width() > bounds.height() ? bounds.height() : bounds.width()) / 2, density); float y = cssValueToDevicePixels(arr[2], bounds.height(), density); float x = cssValueToDevicePixels(arr[3], bounds.width(), density); - canvas.drawCircle(x, y, radius, paint); + clipPathPath.addCircle(x, y, radius, Path.Direction.CW); break; case "ellipse": arr = space.split(value); @@ -603,31 +740,35 @@ private static void drawClipPath(String clipPath, Canvas canvas, Paint paint, Re top = cY - rY; right = (rX * 2) + left; bottom = (rY * 2) + top; - canvas.drawOval(new RectF(left, top, right, bottom), paint); + clipPathPath.addOval(new RectF(left, top, right, bottom), Path.Direction.CW); break; case "polygon": - Path path = new Path(); PointF firstPoint = null; arr = value.split(","); for (String s : arr) { String[] xy = space.split(s.trim()); - PointF point = new PointF(cssValueToDevicePixels(xy[0], bounds.width(), density), cssValueToDevicePixels(xy[1], bounds.height(), density)); + PointF point = new PointF(cssValueToDevicePixels(xy[0], bounds.width(), density), + cssValueToDevicePixels(xy[1], bounds.height(), density)); if (firstPoint == null) { firstPoint = point; - path.moveTo(point.x, point.y); + clipPathPath.moveTo(point.x, point.y); } - path.lineTo(point.x, point.y); + clipPathPath.lineTo(point.x, point.y); } if (firstPoint != null) { - path.lineTo(firstPoint.x, firstPoint.y); + clipPathPath.lineTo(firstPoint.x, firstPoint.y); } - canvas.drawPath(path, paint); break; } } + private void drawClipPath(String clipPath, Canvas canvas, Paint paint, RectF bounds, float density) { + generateClipPath(clipPath, bounds, density); + canvas.drawPath(clipPathPath, paint); + } + private BackgroundDrawParams getDrawParams(float width, float height) { BackgroundDrawParams res = new BackgroundDrawParams(); @@ -663,15 +804,18 @@ private BackgroundDrawParams getDrawParams(float width, float height) { res.sizeX = imageWidth; res.sizeY = imageHeight; - } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && - (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) || ((vx.getUnit() == null || vx.getUnit().isEmpty()) && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { + } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) + && (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) + || ((vx.getUnit() == null || vx.getUnit().isEmpty()) + && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { imageWidth = vx.getValue(); imageHeight = vy.getValue(); res.sizeX = imageWidth; res.sizeY = imageHeight; } - } else if (this.backgroundSizeParsedCSSValues.length == 1 && "ident".equals(this.backgroundSizeParsedCSSValues[0].getType())) { + } else if (this.backgroundSizeParsedCSSValues.length == 1 + && "ident".equals(this.backgroundSizeParsedCSSValues[0].getType())) { float scale = 0; if ("cover".equals(this.backgroundSizeParsedCSSValues[0].getString())) { @@ -702,8 +846,10 @@ private BackgroundDrawParams getDrawParams(float width, float height) { if ("%".equals(vx.getUnit()) && "%".equals(vy.getUnit())) { res.posX = spaceX * vx.getValue() / 100; res.posY = spaceY * vy.getValue() / 100; - } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) && - (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) || ((vx.getUnit() == null || vx.getUnit().isEmpty()) && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { + } else if ("number".equals(vx.getType()) && "number".equals(vy.getType()) + && (("px".equals(vx.getUnit()) && "px".equals(vy.getUnit())) + || ((vx.getUnit() == null || vx.getUnit().isEmpty()) + && (vy.getUnit() == null || vy.getUnit().isEmpty())))) { res.posX = vx.getValue(); res.posY = vy.getValue(); } else if ("ident".equals(vx.getType()) && "ident".equals(vy.getType())) { @@ -751,14 +897,14 @@ private static CSSValue[] parsePosition(CSSValue[] values) { String val = values[0].getString().toLowerCase(Locale.ENGLISH); if ("left".equals(val) || "right".equals(val)) { - result = new CSSValue[]{values[0], center}; + result = new CSSValue[] { values[0], center }; } else if ("top".equals(val) || "bottom".equals(val)) { - result = new CSSValue[]{center, values[0]}; + result = new CSSValue[] { center, values[0] }; } else if ("center".equals(val)) { - result = new CSSValue[]{center, center}; + result = new CSSValue[] { center, center }; } } else if ("number".equals(values[0].getType())) { - result = new CSSValue[]{values[0], center}; + result = new CSSValue[] { values[0], center }; } } @@ -781,29 +927,22 @@ public String toDebugString() { "id: " + this.id + "; " + - "borderTopColor: " + this.borderTopColor + "; " + - "borderRightColor: " + this.borderRightColor + "; " + - "borderBottomColor: " + this.borderBottomColor + "; " + - "borderLeftColor: " + this.borderLeftColor + "; " + + "borderTopColor: " + this.borderTopColor + "; " + "borderRightColor: " + this.borderRightColor + "; " + + "borderBottomColor: " + this.borderBottomColor + "; " + "borderLeftColor: " + this.borderLeftColor + + "; " + - "borderTopWidth: " + this.borderTopWidth + "; " + - "borderRightWidth: " + this.borderRightWidth + "; " + - "borderBottomWidth: " + this.borderBottomWidth + "; " + - "borderLeftWidth: " + this.borderLeftWidth + "; " + + "borderTopWidth: " + this.borderTopWidth + "; " + "borderRightWidth: " + this.borderRightWidth + "; " + + "borderBottomWidth: " + this.borderBottomWidth + "; " + "borderLeftWidth: " + this.borderLeftWidth + + "; " + - "borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + - "borderTopRightRadius: " + this.borderTopRightRadius + "; " + - "borderBottomRightRadius: " + this.borderBottomRightRadius + "; " + - "borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " + + "borderTopLeftRadius: " + this.borderTopLeftRadius + "; " + "borderTopRightRadius: " + + this.borderTopRightRadius + "; " + "borderBottomRightRadius: " + this.borderBottomRightRadius + "; " + + "borderBottomLeftRadius: " + this.borderBottomLeftRadius + "; " + - "clipPath: " + this.clipPath + "; " + - "backgroundColor: " + this.backgroundColor + "; " + - "backgroundImage: " + this.backgroundImage + "; " + - "backgroundBitmap: " + this.backgroundBitmap + "; " + - "backgroundRepeat: " + this.backgroundRepeat + "; " + - "backgroundPosition: " + this.backgroundPosition + "; " + - "backgroundSize: " + this.backgroundSize + "; " - ; + "clipPath: " + this.clipPath + "; " + "backgroundColor: " + this.backgroundColor + "; " + + "backgroundImage: " + this.backgroundImage + "; " + "backgroundBitmap: " + this.backgroundBitmap + + "; " + "backgroundRepeat: " + this.backgroundRepeat + "; " + "backgroundPosition: " + + this.backgroundPosition + "; " + "backgroundSize: " + this.backgroundSize + "; "; } @Override @@ -823,18 +962,72 @@ public Drawable getDrawable() { return drawable; } + private void generateBackgroundOutlinePath(Rect bounds) { + if (backgroundOutlinePath != null && hasCache("backgroundOutlinePath")) { + return; + } + setHasCache("backgroundOutlinePath"); + + if (backgroundOutlinePath == null) { + backgroundOutlinePath = new Path(); + } else { + backgroundOutlinePath.reset(); + } + float[] backgroundRadii = { Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), + Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), + Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), + Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) }; + backgroundOutlinePath.addRoundRect(new RectF(bounds), backgroundRadii, Path.Direction.CW); + } + + private void generateInnerBorderPath(Rect bounds) { + if (innerBorderPath != null && hasCache("innerBorderPath")) { + return; + } + setHasCache("innerBorderPath"); + + if (innerBorderPath == null) { + innerBorderPath = new Path(); + } else { + innerBorderPath.reset(); + } + + float width = (float) bounds.width(); + float height = (float) bounds.height(); + + RectF borderInnerRect = new RectF(borderLeftWidth, borderTopWidth, width - borderRightWidth, + height - borderBottomWidth); + float[] borderInnerRadii = { Math.max(0, borderTopLeftRadius - borderLeftWidth), + Math.max(0, borderTopLeftRadius - borderTopWidth), Math.max(0, borderTopRightRadius - borderRightWidth), + Math.max(0, borderTopRightRadius - borderTopWidth), + Math.max(0, borderBottomRightRadius - borderRightWidth), + Math.max(0, borderBottomRightRadius - borderBottomWidth), + Math.max(0, borderBottomLeftRadius - borderLeftWidth), + Math.max(0, borderBottomLeftRadius - borderBottomWidth) }; + innerBorderPath.addRoundRect(borderInnerRect, borderInnerRadii, Path.Direction.CCW); + } + + public boolean shouldOutline() { + return (android.os.Build.VERSION.SDK_INT >= 21 && getUniformBorderRadius() > 0 && !hasBorderWidth()); + } + @Override public void getOutline(@NonNull Outline outline) { if (android.os.Build.VERSION.SDK_INT >= 21) { - Path backgroundPath = new Path(); - float[] backgroundRadii = { - Math.max(0, borderTopLeftRadius), Math.max(0, borderTopLeftRadius), - Math.max(0, borderTopRightRadius), Math.max(0, borderTopRightRadius), - Math.max(0, borderBottomRightRadius), Math.max(0, borderBottomRightRadius), - Math.max(0, borderBottomLeftRadius), Math.max(0, borderBottomLeftRadius) - }; - backgroundPath.addRoundRect(new RectF(getBounds()), backgroundRadii, Path.Direction.CW); - outline.setConvexPath(backgroundPath); + if (this.clipPath != null) { + // no clip! + generateClipPath(this.clipPath, new RectF(getBounds()), density); + outline.setConvexPath(this.clipPathPath); + } else if (hasUniformBorder()) { + // clip! + outline.setRoundRect(getBounds(), Math.max(0, borderTopLeftRadius)); + } else { + // no clip! + generateBackgroundOutlinePath(getBounds()); + if (backgroundOutlinePath != null) { + outline.setConvexPath(backgroundOutlinePath); + } + } } else { throw new IllegalStateException("Method supported on API 21 or higher"); } @@ -848,4 +1041,4 @@ private class BackgroundDrawParams { private float sizeX; private float sizeY; } -} \ No newline at end of file +} diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java index 7a3abe8ac3..c799227173 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java @@ -1,9 +1,17 @@ /** - * + * */ package org.nativescript.widgets; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; +import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; @@ -11,32 +19,58 @@ import android.view.ViewGroup; import android.widget.FrameLayout; +import android.util.Log; + +import androidx.annotation.RequiresApi; + /** * @author hhristov * */ public abstract class LayoutBase extends ViewGroup { private boolean passThroughParent; + private boolean clipEnabled = true; + private static Paint clipPaint; public LayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setLayerType(View.LAYER_TYPE_HARDWARE, null); + setClipToBounds(clipEnabled); + } + + public LayoutBase(Context context) { + super(context); + init(); + } + + public void setClipToBounds(boolean value) { + clipEnabled = value; + if (value) { + // TODO: does it cost to enable it even if we actually + // will still need to clip? + if (android.os.Build.VERSION.SDK_INT >= 21) { + setClipToOutline(true); + } + } else if (android.os.Build.VERSION.SDK_INT >= 21) { + setClipToOutline(false); + } } - public LayoutBase(Context context) { - super(context); - } - @Override protected LayoutParams generateDefaultLayoutParams() { return new CommonLayoutParams(); } - + /** * {@inheritDoc} */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { - return new CommonLayoutParams(); + return new CommonLayoutParams(); } /** @@ -50,21 +84,21 @@ protected boolean checkLayoutParams(LayoutParams p) { @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { if (from instanceof CommonLayoutParams) - return new CommonLayoutParams((CommonLayoutParams)from); + return new CommonLayoutParams((CommonLayoutParams) from); if (from instanceof FrameLayout.LayoutParams) - return new CommonLayoutParams((FrameLayout.LayoutParams)from); + return new CommonLayoutParams((FrameLayout.LayoutParams) from); if (from instanceof ViewGroup.MarginLayoutParams) - return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); + return new CommonLayoutParams((ViewGroup.MarginLayoutParams) from); return new CommonLayoutParams(from); } - @Override - public boolean shouldDelayChildPressedState() { - return false; - } + @Override + public boolean shouldDelayChildPressedState() { + return false; + } @Override public boolean onTouchEvent(MotionEvent event) { @@ -73,24 +107,25 @@ public boolean onTouchEvent(MotionEvent event) { } // LayoutBase.onTouchEvent(ev) execution means no interactive child view handled - // the event so we let the event pass through to parent view of the layout container + // the event so we let the event pass through to parent view of the layout + // container // because passThroughParent is set to true return false; } - - protected static int getGravity(View view) { - int gravity = -1; - LayoutParams params = view.getLayoutParams(); - if (params instanceof FrameLayout.LayoutParams) { - gravity = ((FrameLayout.LayoutParams)params).gravity; - } - + + protected static int getGravity(View view) { + int gravity = -1; + LayoutParams params = view.getLayoutParams(); + if (params instanceof FrameLayout.LayoutParams) { + gravity = ((FrameLayout.LayoutParams) params).gravity; + } + if (gravity == -1) { gravity = Gravity.FILL; } - + return gravity; - } + } public boolean getPassThroughParent() { return this.passThroughParent; @@ -99,4 +134,45 @@ public boolean getPassThroughParent() { public void setPassThroughParent(boolean value) { this.passThroughParent = value; } -} \ No newline at end of file + + public boolean getClipEnabled() { + return this.clipEnabled; + } + + public void setClipEnabled(boolean value) { + this.clipEnabled = value; + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Override + protected void dispatchDraw(Canvas canvas) { + if (clipEnabled) { + Drawable drawable = getBackground(); + if (drawable instanceof BorderDrawable) { + Path clippingPath = ((BorderDrawable) drawable).getClippingPath(); + // if no clippingPath either it is unnecessary or handled by outline + if (clippingPath != null) { + if (LayoutBase.clipPaint == null) { + LayoutBase.clipPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + // LayoutBase.clipPaint.setColor(Color.WHITE); + LayoutBase.clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } + int saveCount; + int width = getWidth(); + int height = getHeight(); + if (android.os.Build.VERSION.SDK_INT >= 21) { + saveCount = canvas.saveLayer(new android.graphics.RectF(0.0f, 0.0f, width, height), null); + } else { + saveCount = canvas.saveLayer(0.0f, 0.0f, width, height, null, Canvas.ALL_SAVE_FLAG); + } + super.dispatchDraw(canvas); + // we dont use clipPath as it is not antialiased + canvas.drawPath(clippingPath, LayoutBase.clipPaint); + canvas.restoreToCount(saveCount); + return; + } + } + } + super.dispatchDraw(canvas); + } +} diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap index aeb3abb79c..c6e7d79833 100644 --- a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap +++ b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap @@ -11,7 +11,7 @@ exports[`svelte configuration for android 1`] = ` node: false }, devtool: 'inline-source-map', - target: 'node', + target: 'electron-main', watchOptions: { ignored: [ '__jest__/platforms/**', @@ -33,7 +33,8 @@ exports[`svelte configuration for android 1`] = ` symlinks: true, alias: { '~': '__jest__/src', - '@': '__jest__/src' + '@': '__jest__/src', + 'tns-core-modules': '@nativescript/core' }, extensions: [ '.android.svelte', @@ -114,7 +115,7 @@ exports[`svelte configuration for android 1`] = ` }, /* config.module.rule('workers') */ { - test: /\\\\.(js|ts)$/, + test: /\\\\.(js|ts|svelte)$/, use: [ /* config.module.rule('workers').use('nativescript-worker-loader') */ { @@ -338,7 +339,7 @@ exports[`svelte configuration for ios 1`] = ` node: false }, devtool: 'inline-source-map', - target: 'node', + target: 'electron-main', watchOptions: { ignored: [ '__jest__/platforms/**', @@ -360,7 +361,8 @@ exports[`svelte configuration for ios 1`] = ` symlinks: true, alias: { '~': '__jest__/src', - '@': '__jest__/src' + '@': '__jest__/src', + 'tns-core-modules': '@nativescript/core' }, extensions: [ '.ios.svelte', @@ -441,7 +443,7 @@ exports[`svelte configuration for ios 1`] = ` }, /* config.module.rule('workers') */ { - test: /\\\\.(js|ts)$/, + test: /\\\\.(js|ts|svelte)$/, use: [ /* config.module.rule('workers').use('nativescript-worker-loader') */ { diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json index 6cfae4b398..21ad106634 100644 --- a/packages/webpack5/package.json +++ b/packages/webpack5/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/webpack", - "version": "5.0.0-rc.1", + "version": "5.0.0-rc.3", "private": false, "main": "dist/index.js", "files": [ diff --git a/packages/webpack5/src/configuration/svelte.ts b/packages/webpack5/src/configuration/svelte.ts index 1f1dbba2ad..c394cbbb4d 100644 --- a/packages/webpack5/src/configuration/svelte.ts +++ b/packages/webpack5/src/configuration/svelte.ts @@ -1,10 +1,11 @@ import Config from 'webpack-chain'; -import { getProjectFilePath, getProjectRootPath } from '../helpers/project'; +import { getProjectFilePath } from '../helpers/project'; import { getPlatformName } from '../helpers/platform'; import { env as _env, IWebpackEnv } from '../index'; import { error } from '../helpers/log'; import base from './base'; +import { hasDependency } from '../helpers/dependencies'; export default function (config: Config, env: IWebpackEnv = _env): Config { base(config, env); @@ -13,35 +14,75 @@ export default function (config: Config, env: IWebpackEnv = _env): Config { const mode = env.production ? 'production' : 'development'; const production = mode === 'production'; + // target('node') is the default but causes svelte-loader to detect it as a "server" render, disabling HMR + // electron-main sneaks us past the target == 'node' check and gets us HMR + config.target('electron-main'); + + // svelte-hmr still references tns-core-modules, so we shim it here for compat. + config.resolve.alias.set('tns-core-modules', '@nativescript/core'); + // resolve .svelte files // the order is reversed because we are using prepend! config.resolve.extensions.prepend('.svelte').prepend(`.${platform}.svelte`); + + // add worker support to .svelte files (new Worker('./path')) + config.module.rule('workers').test(/\.(js|ts|svelte)$/); + // add a rule for .svelte files - config.module - .rule('svelte') - .test(/\.svelte$/) - .exclude.add(/node_modules/) - .end() - .use('svelte-loader-hot') - .loader('svelte-loader-hot') - .tap((options) => { - return { - ...options, - dev: !production, - preprocess: getSvelteConfigPreprocessor(), - hotReload: !production, - hotOptions: { - injectCss: false, - native: true, - }, - // Suppress A11y warnings - onwarn(warning, warn) { - if (!/A11y:/.test(warning.message)) { - warn(warning); - } - }, - }; - }); + if (hasDependency('svelte-loader')) { + // configure using svelte-loader + config.module + .rule('svelte') + .test(/\.svelte$/) + .exclude.add(/node_modules/) + .end() + .use('svelte-loader') + .loader('svelte-loader') + .tap((options) => { + const svelteConfig = getSvelteConfig(); + return { + ...options, + compilerOptions: { + ...svelteConfig?.compilerOptions, + dev: !production, + }, + preprocess: svelteConfig?.preprocess, + hotReload: !production, + hotOptions: { + injectCss: false, + native: true, + }, + }; + }); + } else { + // fallback for projects still using svelte-loader-hot + config.module + .rule('svelte') + .test(/\.svelte$/) + .exclude.add(/node_modules/) + .end() + .use('svelte-loader-hot') + .loader('svelte-loader-hot') + .tap((options) => { + const svelteConfig = getSvelteConfig(); + return { + ...options, + dev: !production, + preprocess: getSvelteConfigPreprocessor(), + hotReload: !production, + hotOptions: { + injectCss: false, + native: true, + }, + // Suppress A11y warnings + onwarn(warning, warn) { + if (!/A11y:/.test(warning.message)) { + warn(warning); + } + }, + }; + }); + } return config; } @@ -54,6 +95,7 @@ function getSvelteConfigPreprocessor(): any { interface ISvelteConfig { preprocess: any; + compilerOptions: any; } function getSvelteConfig(): ISvelteConfig | undefined { diff --git a/workspace.json b/workspace.json index 928e9d52bc..92047ea9da 100644 --- a/workspace.json +++ b/workspace.json @@ -271,9 +271,7 @@ "builder": "@nrwl/workspace:run-commands", "outputs": ["dist/packages"], "options": { - "commands": [ - "npm run build" - ], + "commands": ["npm run build"], "cwd": "packages/webpack5", "parallel": false }