backgroundImage property now use Fetcher & Cache as Image component (#4030)

* backgroundImage property now use Fetcher & Cache as Image component
Fix GridLayout tests on iPhone Plus - actualLength wasn’t rounded
ImageCache is closed when activity is stopped

* Fix reset of background drawable.

* additional check for drawable

* imageCache init cache on activity Started
This commit is contained in:
Hristo Hristov
2017-04-21 16:50:12 +03:00
committed by GitHub
parent 483df829c9
commit 9e3222781a
18 changed files with 403 additions and 396 deletions

View File

@ -63,5 +63,5 @@ function reset(page: Page) {
page.css = "";
page.actionBarHidden = false;
page.backgroundSpanUnderStatusBar = false;
page.actionBar.style.backgroundColor = unsetValue;
page.actionBar.backgroundColor = unsetValue;
}

View File

@ -35,7 +35,7 @@ import * as platformTests from "./platform/platform-tests";
allTests["PLATFORM"] = platformTests;
import * as fsTests from "./file-system/file-system-tests";
allTests["FILE SYSTEM"] = fsTests;
allTests["FILE-SYSTEM"] = fsTests;
import * as httpTests from "./http/http-tests";
allTests["HTTP"] = httpTests;
@ -47,13 +47,13 @@ import * as fetchTests from "./fetch/fetch-tests";
allTests["FETCH"] = fetchTests;
import * as appSettingsTests from "./application-settings/application-settings-tests";
allTests["APPLICATION SETTINGS"] = appSettingsTests;
allTests["APPLICATION-SETTINGS"] = appSettingsTests;
import * as applicationTests from "./application/application-tests";
allTests["APPLICATION"] = applicationTests;
import * as imageSourceTests from "./image-source/image-source-tests";
allTests["IMAGE SOURCE"] = imageSourceTests;
allTests["IMAGE-SOURCE"] = imageSourceTests;
import * as observableArrayTests from "./data/observable-array-tests";
allTests["OBSERVABLE-ARRAY"] = observableArrayTests;

View File

@ -17,12 +17,12 @@ import * as ViewModule from "tns-core-modules/ui/core/view";
import * as helper from "../helper";
import * as ObservableModule from "tns-core-modules/data/observable";
import * as color from "tns-core-modules/color";
import * as backgroundModule from "tns-core-modules/ui/styling/background";
import { android as androidApp } from "tns-core-modules/application";
const imagePath = "~/logo.png";
if (isAndroid) {
const imageModule = require("ui/image");
imageModule.currentMode = imageModule.CacheMode.memory; // use memory cache only.
(<any>backgroundModule).initImageCache(androidApp.startActivity, (<any>backgroundModule).CacheMode.memory); // use memory cache only.
}
export const test_Image_Members = function () {

View File

@ -248,7 +248,7 @@ function initComponentCallbacks() {
},
onConfigurationChanged: function (newConfig: android.content.res.Configuration) {
let newOrientation = newConfig.orientation;
const newOrientation = newConfig.orientation;
if (newOrientation === currentOrientation) {
return;
}

View File

@ -27,6 +27,7 @@ function getApplication() {
return application;
}
function getResources() {
if (!resources) {
resources = getApplication().getResources();
@ -248,7 +249,7 @@ export function fromUrl(url: string): Promise<ImageSource> {
export function fromFileOrResource(path: string): ImageSource {
if (!isFileOrResourcePath(path)) {
throw new Error("Path \"" + "\" is not a valid file or resource.");
throw new Error(`${path} is not a valid file or resource.`);
}
if (path.indexOf(RESOURCE_PREFIX) === 0) {

View File

@ -403,9 +403,12 @@ export class View extends ViewCommon {
if (value instanceof UIColor) {
this.nativeView.backgroundColor = value;
} else {
this.nativeView.backgroundColor = ios.createBackgroundUIColor(this);
ios.createBackgroundUIColor(this, (color: UIColor) => {
this.nativeView.backgroundColor = color;
});
this._setNativeClipToBounds();
}
if (!updateSuspended) {
CATransaction.commit();
}

View File

@ -9,44 +9,6 @@ export * from "./image-common";
const FILE_PREFIX = "file:///";
const ASYNC = "async";
let imageFetcher: org.nativescript.widgets.image.Fetcher;
let imageCache: org.nativescript.widgets.image.Cache;
export enum CacheMode {
none,
memory,
diskAndMemory
}
export let currentCacheMode: CacheMode;
export function initImageCache(context: android.content.Context, mode = CacheMode.diskAndMemory, memoryCacheSize: number = 0.25, diskCacheSize: number = 10 * 1024 * 1024): void {
if (currentCacheMode === mode) {
return;
}
currentCacheMode = mode;
if (!imageFetcher) {
imageFetcher = org.nativescript.widgets.image.Fetcher.getInstance(context);
}
// Disable cache.
if (mode === CacheMode.none) {
if (imageCache != null && imageFetcher != null) {
imageFetcher.clearCache();
}
}
let params = new org.nativescript.widgets.image.Cache.CacheParams();
params.memoryCacheEnabled = mode !== CacheMode.none;
params.setMemCacheSizePercent(memoryCacheSize); // Set memory cache to % of app memory
params.diskCacheEnabled = mode === CacheMode.diskAndMemory;
params.diskCacheSize = diskCacheSize;
imageCache = org.nativescript.widgets.image.Cache.getInstance(params);
imageFetcher.addImageCache(imageCache);
imageFetcher.initCache();
}
interface ImageLoadedListener {
new (owner: Image): org.nativescript.widgets.image.Worker.OnImageLoadedListener;
}
@ -84,9 +46,6 @@ export class Image extends ImageBase {
public createNativeView() {
initializeImageLoadedListener();
if (!imageFetcher) {
initImageCache(this._context);
}
const imageView = new org.nativescript.widgets.ImageView(this._context);
const listener = new ImageLoadedListener(this);
@ -124,12 +83,10 @@ export class Image extends ImageBase {
if (isDataURI(value)) {
// TODO: Check with runtime what should we do in case of base64 string.
super._createImageSourceFromSrc();
}
else if (isFileOrResourcePath(value)) {
} else if (isFileOrResourcePath(value)) {
if (value.indexOf(RESOURCE_PREFIX) === 0) {
imageView.setUri(value, this.decodeWidth, this.decodeHeight, this.useCache, async);
}
else {
} else {
let fileName = value;
if (fileName.indexOf("~/") === 0) {
fileName = path.join(knownFolders.currentApp().path, fileName.replace("~/", ""));
@ -137,8 +94,7 @@ export class Image extends ImageBase {
imageView.setUri(FILE_PREFIX + fileName, this.decodeWidth, this.decodeHeight, this.useCache, async);
}
}
else {
} else {
// For backwards compatibility http always use async loading.
imageView.setUri(value, this.decodeWidth, this.decodeHeight, this.useCache, true);
}

View File

@ -100,12 +100,13 @@ export class Label extends TextBase implements LabelDefinition {
}
[backgroundInternalProperty.setNative](value: Background) {
if (value instanceof Background) {
const uiColor = <UIColor>ios.createBackgroundUIColor(this, true);
value = uiColor ? uiColor.CGColor : null;
ios.createBackgroundUIColor(this, (color: UIColor) => {
const cgColor = color ? color.CGColor : null;
this.nativeView.layer.backgroundColor = cgColor;
}, true);
}
this._setNativeClipToBounds();
this.nativeView.layer.backgroundColor = value;
}
[borderTopWidthProperty.setNative](value: Length) {

View File

@ -174,7 +174,7 @@ export class GridLayout extends GridLayoutBase {
actualLength = offset - roundedOffset;
roundedLength = Math.round(actualLength);
columnGroup.rowOrColumn._actualLength = layout.toDeviceIndependentPixels(roundedLength);
columnGroup.rowOrColumn._actualLength = layout.round(layout.toDeviceIndependentPixels(roundedLength));
roundedOffset += roundedLength;
this.columnOffsets.push(roundedOffset);
@ -191,7 +191,7 @@ export class GridLayout extends GridLayoutBase {
actualLength = offset - roundedOffset;
roundedLength = Math.round(actualLength);
rowGroup.rowOrColumn._actualLength = layout.toDeviceIndependentPixels(roundedLength);
rowGroup.rowOrColumn._actualLength = layout.round(layout.toDeviceIndependentPixels(roundedLength));
roundedOffset += roundedLength;
this.rowOffsets.push(roundedOffset);

View File

@ -94,7 +94,6 @@ export class Page extends PageBase {
const layout = new org.nativescript.widgets.GridLayout(this._context);
layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));
layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
layout.setBackgroundColor(-1);
return layout;
}

View File

@ -1,17 +1,15 @@
// Deifinitions.
import { Background as BackgroundDefinition, BackgroundDrawParams } from "./background";
import { Background as BackgroundDefinition } from "./background";
import { BackgroundRepeat } from "../core/view";
import { ImageSource } from "../../image-source";
// Types.
import { Color } from "../../color";
import { CSSValue, parse as cssParse } from "../../css-value";
export class Background implements BackgroundDefinition {
public static default = new Background();
public color: Color;
public image: ImageSource;
public image: string;
public repeat: BackgroundRepeat;
public position: string;
public size: string;
@ -30,7 +28,7 @@ export class Background implements BackgroundDefinition {
public clipPath: string;
private clone(): Background {
let clone = new Background();
const clone = new Background();
clone.color = this.color;
clone.image = this.image;
@ -55,269 +53,113 @@ export class Background implements BackgroundDefinition {
}
public withColor(value: Color): Background {
let clone = this.clone();
const clone = this.clone();
clone.color = value;
return clone;
}
public withImage(value: ImageSource): Background {
let clone = this.clone();
public withImage(value: string): Background {
const clone = this.clone();
clone.image = value;
return clone;
}
public withRepeat(value: BackgroundRepeat): Background {
let clone = this.clone();
const clone = this.clone();
clone.repeat = value;
return clone;
}
public withPosition(value: string): Background {
let clone = this.clone();
const clone = this.clone();
clone.position = value;
return clone;
}
public withSize(value: string): Background {
let clone = this.clone();
const clone = this.clone();
clone.size = value;
return clone;
}
public withBorderTopColor(value: Color): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderTopColor = value;
return clone;
}
public withBorderRightColor(value: Color): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderRightColor = value;
return clone;
}
public withBorderBottomColor(value: Color): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderBottomColor = value;
return clone;
}
public withBorderLeftColor(value: Color): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderLeftColor = value;
return clone;
}
public withBorderTopWidth(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderTopWidth = value;
return clone;
}
public withBorderRightWidth(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderRightWidth = value;
return clone;
}
public withBorderBottomWidth(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderBottomWidth = value;
return clone;
}
public withBorderLeftWidth(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderLeftWidth = value;
return clone;
}
public withBorderTopLeftRadius(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderTopLeftRadius = value;
return clone;
}
public withBorderTopRightRadius(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderTopRightRadius = value;
return clone;
}
public withBorderBottomRightRadius(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderBottomRightRadius = value;
return clone;
}
public withBorderBottomLeftRadius(value: number): Background {
let clone = this.clone();
const clone = this.clone();
clone.borderBottomLeftRadius = value;
return clone;
}
public withClipPath(value: string): Background {
let clone = this.clone();
const clone = this.clone();
clone.clipPath = value;
return clone;
}
public getDrawParams(width: number, height: number): BackgroundDrawParams {
if (!this.image) {
return null;
}
let res: BackgroundDrawParams = {
repeatX: true,
repeatY: true,
posX: 0,
posY: 0,
}
// repeat
if (this.repeat) {
switch (this.repeat.toLowerCase()) {
case "no-repeat":
res.repeatX = false;
res.repeatY = false;
break;
case "repeat-x":
res.repeatY = false;
break;
case "repeat-y":
res.repeatX = false;
break;
}
}
let imageWidth = this.image.width;
let imageHeight = this.image.height;
// size
if (this.size) {
let values = cssParse(this.size);
if (values.length === 2) {
let vx = values[0];
let vy = values[1];
if (vx.unit === "%" && vy.unit === "%") {
imageWidth = width * vx.value / 100;
imageHeight = height * vy.value / 100;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
else if (vx.type === "number" && vy.type === "number" &&
((vx.unit === "px" && vy.unit === "px") || (vx.unit === "" && vy.unit === ""))) {
imageWidth = vx.value;
imageHeight = vy.value;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
else if (values.length === 1 && values[0].type === "ident") {
let scale = 0;
if (values[0].string === "cover") {
scale = Math.max(width / imageWidth, height / imageHeight);
}
else if (values[0].string === "contain") {
scale = Math.min(width / imageWidth, height / imageHeight);
}
if (scale > 0) {
imageWidth *= scale;
imageHeight *= scale;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
}
// position
if (this.position) {
let v = Background.parsePosition(this.position);
if (v) {
let spaceX = width - imageWidth;
let spaceY = height - imageHeight;
if (v.x.unit === "%" && v.y.unit === "%") {
res.posX = spaceX * v.x.value / 100;
res.posY = spaceY * v.y.value / 100;
}
else if (v.x.type === "number" && v.y.type === "number" &&
((v.x.unit === "px" && v.y.unit === "px") || (v.x.unit === "" && v.y.unit === ""))) {
res.posX = v.x.value;
res.posY = v.y.value;
}
else if (v.x.type === "ident" && v.y.type === "ident") {
if (v.x.string.toLowerCase() === "center") {
res.posX = spaceX / 2;
}
else if (v.x.string.toLowerCase() === "right") {
res.posX = spaceX;
}
if (v.y.string.toLowerCase() === "center") {
res.posY = spaceY / 2;
}
else if (v.y.string.toLowerCase() === "bottom") {
res.posY = spaceY;
}
}
}
}
return res;
}
private static parsePosition(pos: string): { x: CSSValue, y: CSSValue } {
let values = cssParse(pos);
if (values.length === 2) {
return {
x: values[0],
y: values[1]
};
}
if (values.length === 1 && values[0].type === "ident") {
let val = values[0].string.toLocaleLowerCase();
let center = {
type: "ident",
string: "center"
};
// If you only one keyword is specified, the other value is "center"
if (val === "left" || val === "right") {
return {
x: values[0],
y: center
};
}
else if (val === "top" || val === "bottom") {
return {
x: center,
y: values[0]
};
}
else if (val === "center") {
return {
x: center,
y: center
};
}
}
return null;
};
public isEmpty(): boolean {
return !this.color
&& !this.image

View File

@ -1,10 +1,14 @@
import { View } from "../core/view";
import { isNullOrUndefined, isFunction, getClass } from "../../utils/types";
import { CacheLayerType, layout } from "../../utils/utils";
import { CacheLayerType, isDataURI, isFileOrResourcePath, layout, RESOURCE_PREFIX, FILE_PREFIX } from "../../utils/utils";
import { parse } from "../../css-value";
import { path, knownFolders } from "../../file-system";
import { android as androidApp } from "../../application";
export * from "./background-common"
interface AndroidView {
background: android.graphics.drawable.Drawable.ConstantState;
}
// TODO: Change this implementation to use
// We are using "ad" here to avoid namespace collision with the global android object
export module ad {
@ -17,8 +21,6 @@ export module ad {
return SDK;
}
let _defaultBackgrounds = new Map<string, android.graphics.drawable.Drawable.ConstantState>();
function isSetColorFilterOnlyWidget(nativeView: android.view.View): boolean {
return (
nativeView instanceof android.widget.Button ||
@ -29,43 +31,40 @@ export module ad {
}
export function onBackgroundOrBorderPropertyChanged(view: View) {
let nativeView = <android.view.View>view.nativeView;
const nativeView = <android.view.View>view.nativeView;
if (!nativeView) {
return;
}
let background = view.style.backgroundInternal;
let backgroundDrawable = nativeView.getBackground();
let cache = <CacheLayerType>view.nativeView;
let viewClass = getClass(view);
// always cache the default background constant state.
if (!_defaultBackgrounds.has(viewClass) && !isNullOrUndefined(backgroundDrawable)) {
_defaultBackgrounds.set(viewClass, backgroundDrawable.getConstantState());
const background = view.style.backgroundInternal;
const cache = <CacheLayerType>view.nativeView;
const drawable = nativeView.getBackground();
const androidView = <any>view as AndroidView;
// use undefined as not set. getBackground will never return undefined only Drawable or null;
if (androidView.background === undefined && drawable) {
androidView.background = drawable.getConstantState();
}
if (isSetColorFilterOnlyWidget(nativeView)
&& !isNullOrUndefined(backgroundDrawable)
&& isFunction(backgroundDrawable.setColorFilter)
&& drawable
&& !background.hasBorderWidth()
&& !background.hasBorderRadius()
&& !background.clipPath
&& isNullOrUndefined(background.image)
&& !isNullOrUndefined(background.color)) {
let backgroundColor = (<any>backgroundDrawable).backgroundColor = background.color.android;
backgroundDrawable.mutate();
backgroundDrawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN);
backgroundDrawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar
(<any>backgroundDrawable).backgroundColor = backgroundColor;
}
else if (!background.isEmpty()) {
if (!(backgroundDrawable instanceof org.nativescript.widgets.BorderDrawable)) {
&& !background.image
&& background.color) {
const backgroundColor = (<any>drawable).backgroundColor = background.color.android;
drawable.mutate();
drawable.setColorFilter(backgroundColor, android.graphics.PorterDuff.Mode.SRC_IN);
drawable.invalidateSelf(); // Make sure the drawable is invalidated. Android forgets to invalidate it in some cases: toolbar
(<any>drawable).backgroundColor = backgroundColor;
} else if (!background.isEmpty()) {
let backgroundDrawable = drawable as org.nativescript.widgets.BorderDrawable;
if (!(drawable instanceof org.nativescript.widgets.BorderDrawable)) {
backgroundDrawable = new org.nativescript.widgets.BorderDrawable(layout.getDisplayDensity(), view.toString());
refreshBorderDrawable(view, <org.nativescript.widgets.BorderDrawable>backgroundDrawable);
refreshBorderDrawable(view, backgroundDrawable);
org.nativescript.widgets.ViewHelper.setBackground(nativeView, backgroundDrawable);
}
else {
refreshBorderDrawable(view, <org.nativescript.widgets.BorderDrawable>backgroundDrawable);
} else {
refreshBorderDrawable(view, backgroundDrawable);
}
// This should be done only when backgroundImage is set!!!
@ -77,11 +76,11 @@ export module ad {
cache.setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null);
}
}
}
else {
if (_defaultBackgrounds.has(viewClass)) {
org.nativescript.widgets.ViewHelper.setBackground(nativeView, _defaultBackgrounds.get(viewClass).newDrawable());
}
} else {
// TODO: newDrawable for BitmapDrawable will fail if we don't speicfy resource. Use the other overload.
const defaultDrawable = androidView.background ? androidView.background.newDrawable() : null;
org.nativescript.widgets.ViewHelper.setBackground(nativeView, defaultDrawable);
androidView.background = undefined;
if (cache.layerType !== undefined) {
cache.setLayerType(cache.layerType, null);
@ -91,10 +90,10 @@ export module ad {
// TODO: Can we move BorderWidths as separate native setter?
// This way we could skip setPadding if borderWidth is not changed.
let leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft);
let topPadding = Math.ceil(view.effectiveBorderTopWidth + view.effectivePaddingTop);
let rightPadding = Math.ceil(view.effectiveBorderRightWidth + view.effectivePaddingRight);
let bottomPadding = Math.ceil(view.effectiveBorderBottomWidth + view.effectivePaddingBottom);
const leftPadding = Math.ceil(view.effectiveBorderLeftWidth + view.effectivePaddingLeft);
const topPadding = Math.ceil(view.effectiveBorderTopWidth + view.effectivePaddingTop);
const rightPadding = Math.ceil(view.effectiveBorderRightWidth + view.effectivePaddingRight);
const bottomPadding = Math.ceil(view.effectiveBorderBottomWidth + view.effectivePaddingBottom);
nativeView.setPadding(
leftPadding,
@ -105,25 +104,53 @@ export module ad {
}
}
function refreshBorderDrawable(view: View, borderDrawable: org.nativescript.widgets.BorderDrawable) {
let background = view.style.backgroundInternal;
function fromBase64(source: string): android.graphics.Bitmap {
const bytes = android.util.Base64.decode(source, android.util.Base64.DEFAULT);
return android.graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.length)
}
const pattern: RegExp = /url\(('|")(.*?)\1\)/;
function refreshBorderDrawable(this: void, view: View, borderDrawable: org.nativescript.widgets.BorderDrawable) {
const nativeView = <android.view.View>view.nativeView;
const context = nativeView.getContext();
const background = view.style.backgroundInternal;
if (background) {
let backgroundPositionParsedCSSValues: native.Array<org.nativescript.widgets.CSSValue> = null;
let backgroundSizeParsedCSSValues: native.Array<org.nativescript.widgets.CSSValue> = null;
if (background.position) {
backgroundPositionParsedCSSValues = createNativeCSSValueArray(background.position);
const backgroundPositionParsedCSSValues = createNativeCSSValueArray(background.position);
const backgroundSizeParsedCSSValues = createNativeCSSValueArray(background.size);
const blackColor = -16777216; //android.graphics.Color.BLACK;
let imageUri = background.image;
if (imageUri) {
const match = imageUri.match(pattern);
if (match && match[2]) {
imageUri = match[2];
}
}
let bitmap: android.graphics.Bitmap = null;
if (isDataURI(imageUri)) {
const base64Data = imageUri.split(",")[1];
if (base64Data !== undefined) {
bitmap = fromBase64(base64Data);
imageUri = null;
}
} else if (isFileOrResourcePath(imageUri)) {
if (imageUri.indexOf(RESOURCE_PREFIX) !== 0) {
let fileName = imageUri;
if (fileName.indexOf("~/") === 0) {
fileName = path.join(knownFolders.currentApp().path, fileName.replace("~/", ""));
}
imageUri = FILE_PREFIX + fileName;
}
if (background.size) {
backgroundSizeParsedCSSValues = createNativeCSSValueArray(background.size);
}
let blackColor = android.graphics.Color.BLACK;
borderDrawable.refresh(
(background.borderTopColor && background.borderTopColor.android !== undefined) ? background.borderTopColor.android : blackColor,
(background.borderRightColor && background.borderRightColor.android !== undefined) ? background.borderRightColor.android : blackColor,
(background.borderBottomColor && background.borderBottomColor.android !== undefined) ? background.borderBottomColor.android : blackColor,
(background.borderLeftColor && background.borderLeftColor.android !== undefined) ? background.borderLeftColor.android : blackColor,
background.borderTopColor ? background.borderTopColor.android : blackColor,
background.borderRightColor ? background.borderRightColor.android : blackColor,
background.borderBottomColor ? background.borderBottomColor.android : blackColor,
background.borderLeftColor ? background.borderLeftColor.android : blackColor,
background.borderTopWidth,
background.borderRightWidth,
@ -137,8 +164,10 @@ function refreshBorderDrawable(view: View, borderDrawable: org.nativescript.widg
background.clipPath,
(background.color && background.color.android) ? background.color.android : 0,
(background.image && background.image.android) ? background.image.android : null,
background.color ? background.color.android : 0,
imageUri,
bitmap,
context,
background.repeat,
background.position,
backgroundPositionParsedCSSValues,
@ -154,8 +183,8 @@ function createNativeCSSValueArray(css: string): native.Array<org.nativescript.w
return null;
}
let cssValues = parse(css);
let nativeArray = Array.create(org.nativescript.widgets.CSSValue, cssValues.length);
const cssValues = parse(css);
const nativeArray = Array.create(org.nativescript.widgets.CSSValue, cssValues.length);
for (let i = 0, length = cssValues.length; i < length; i++) {
nativeArray[i] = new org.nativescript.widgets.CSSValue(
cssValues[i].type,
@ -167,3 +196,48 @@ function createNativeCSSValueArray(css: string): native.Array<org.nativescript.w
return nativeArray;
}
export enum CacheMode {
none,
memory,
diskAndMemory
}
let currentCacheMode: CacheMode;
let imageFetcher: org.nativescript.widgets.image.Fetcher;
export function initImageCache(context: android.content.Context, mode = CacheMode.diskAndMemory, memoryCacheSize: number = 0.25, diskCacheSize: number = 10 * 1024 * 1024): void {
if (currentCacheMode === mode) {
return;
}
currentCacheMode = mode;
if (!imageFetcher) {
imageFetcher = org.nativescript.widgets.image.Fetcher.getInstance(context);
} else {
imageFetcher.clearCache();
}
const params = new org.nativescript.widgets.image.Cache.CacheParams();
params.memoryCacheEnabled = mode !== CacheMode.none;
params.setMemCacheSizePercent(memoryCacheSize); // Set memory cache to % of app memory
params.diskCacheEnabled = mode === CacheMode.diskAndMemory;
params.diskCacheSize = diskCacheSize;
const imageCache = org.nativescript.widgets.image.Cache.getInstance(params);
imageFetcher.addImageCache(imageCache);
imageFetcher.initCache();
}
androidApp.on("activityStarted", (args) => {
if (!imageFetcher) {
initImageCache(args.activity);
} else {
imageFetcher.initCache();
}
});
androidApp.on("activityStopped", (args) => {
if (imageFetcher) {
imageFetcher.closeCache();
}
});

View File

@ -2,23 +2,13 @@
* @module "ui/styling/background"
*/ /** */
import { ImageSource } from "../../image-source";
import { Color } from "../../color";
import { View, BackgroundRepeat } from "../core/view";
export interface BackgroundDrawParams {
repeatX: boolean;
repeatY: boolean;
posX: number;
posY: number;
sizeX?: number;
sizeY?: number;
}
export class Background {
public static default: Background;
public color: Color;
public image: ImageSource;
public image: string;
public repeat: BackgroundRepeat;
public position: string;
public size: string;
@ -37,7 +27,7 @@ export class Background {
public clipPath: string;
public withColor(value: Color): Background;
public withImage(value: ImageSource): Background;
public withImage(value: string): Background;
public withRepeat(value: BackgroundRepeat): Background;
public withPosition(value: string): Background;
public withSize(value: string): Background;
@ -55,8 +45,6 @@ export class Background {
public withBorderBottomLeftRadius(value: number): Background;
public withClipPath(value: string): Background;
public getDrawParams(width: number, height: number): BackgroundDrawParams;
public isEmpty(): boolean;
public static equals(value1: Background, value2: Background): boolean;
@ -74,7 +62,7 @@ export class Background {
}
export module ios {
export function createBackgroundUIColor(view: View, flip?: boolean): any /* UIColor */;
export function createBackgroundUIColor(view: View, callback: (uiColor: any /* UIColor */) => void, flip?: boolean): void;
}
export module ad {

View File

@ -1,8 +1,9 @@
import { Color } from "../../color";
import { Background as BackgroundDefinition } from "./background";
import { View, Point } from "../core/view";
import { Background } from "./background-common";
import { ios as utilsIos } from "../../utils/utils";
import { layout } from "../../utils/utils";
import { Color } from "../../color";
import { ios as utilsIos, isDataURI, isFileOrResourcePath, layout } from "../../utils/utils";
import { fromFileOrResource, fromBase64, fromUrl } from "../../image-source";
import { CSSValue, parse as cssParse } from "../../css-value";
export * from "./background-common";
@ -22,10 +23,11 @@ interface Rect {
}
const clearCGColor = utilsIos.getter(UIColor, UIColor.clearColor).CGColor;
const symbolUrl = Symbol("backgroundImageUrl");
export module ios {
export function createBackgroundUIColor(view: View, flip?: boolean): UIColor {
const background = <Background>view.style.backgroundInternal;
export function createBackgroundUIColor(view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
const background = view.style.backgroundInternal;
const nativeView = <NativeView>view.nativeView;
if (background.hasUniformBorder()) {
@ -52,9 +54,10 @@ export module ios {
}
if (!background.image) {
return background.color ? background.color.ios : undefined;
const uiColor = background.color ? background.color.ios : undefined;
callback(uiColor);
} else {
return getUIColorFromImage(view, nativeView, background, flip);
setUIColorFromImage(view, nativeView, callback, flip);
}
}
}
@ -77,7 +80,8 @@ function clearNonUniformBorders(nativeView: NativeView): void {
}
}
function getUIColorFromImage(view: View, nativeView: UIView, background: Background, flip?: boolean): UIColor {
const pattern: RegExp = /url\(('|")(.*?)\1\)/;
function setUIColorFromImage(view: View, nativeView: UIView, callback: (uiColor: UIColor) => void, flip?: boolean): void {
const frame = nativeView.frame;
const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height;
@ -85,9 +89,188 @@ function getUIColorFromImage(view: View, nativeView: UIView, background: Backgro
return undefined;
}
// We have an image for a background
let img = <UIImage>background.image.ios;
const params = background.getDrawParams(boundsWidth, boundsHeight);
const style = view.style;
const background = style.backgroundInternal;
let imageUri = background.image;
if (imageUri) {
const match = imageUri.match(pattern);
if (match && match[2]) {
imageUri = match[2];
}
}
let bitmap: UIImage;
if (isDataURI(imageUri)) {
const base64Data = imageUri.split(",")[1];
if (base64Data !== undefined) {
bitmap = fromBase64(base64Data).ios
}
} else if (isFileOrResourcePath(imageUri)) {
bitmap = fromFileOrResource(imageUri).ios;
} else if (imageUri.indexOf("http") !== -1) {
style[symbolUrl] = imageUri;
fromUrl(imageUri).then((r) => {
if (style && style[symbolUrl] === imageUri) {
uiColorFromImage(r.ios, view, callback, flip);
}
});
}
uiColorFromImage(bitmap, view, callback, flip);
}
interface BackgroundDrawParams {
repeatX: boolean;
repeatY: boolean;
posX: number;
posY: number;
sizeX?: number;
sizeY?: number;
}
function parsePosition(pos: string): { x: CSSValue, y: CSSValue } {
const values = cssParse(pos);
if (values.length === 2) {
return { x: values[0], y: values[1] };
}
if (values.length === 1 && values[0].type === "ident") {
const val = values[0].string.toLocaleLowerCase();
const center = { type: "ident", string: "center" };
// If you only one keyword is specified, the other value is "center"
if (val === "left" || val === "right") {
return { x: values[0], y: center };
} else if (val === "top" || val === "bottom") {
return { x: center, y: values[0] };
} else if (val === "center") {
return { x: center, y: center };
}
}
return null;
};
function getDrawParams(this: void, image: UIImage, background: BackgroundDefinition, width: number, height: number): BackgroundDrawParams {
if (!image) {
return null;
}
const res: BackgroundDrawParams = {
repeatX: true,
repeatY: true,
posX: 0,
posY: 0,
}
// repeat
if (background.repeat) {
switch (background.repeat.toLowerCase()) {
case "no-repeat":
res.repeatX = false;
res.repeatY = false;
break;
case "repeat-x":
res.repeatY = false;
break;
case "repeat-y":
res.repeatX = false;
break;
}
}
const imageSize = image.size;
let imageWidth = imageSize.width;
let imageHeight = imageSize.height;
// size
const size = background.size;
if (size) {
const values = cssParse(size);
if (values.length === 2) {
const vx = values[0];
const vy = values[1];
if (vx.unit === "%" && vy.unit === "%") {
imageWidth = width * vx.value / 100;
imageHeight = height * vy.value / 100;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
} else if (vx.type === "number" && vy.type === "number" &&
((vx.unit === "px" && vy.unit === "px") || (vx.unit === "" && vy.unit === ""))) {
imageWidth = vx.value;
imageHeight = vy.value;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
else if (values.length === 1 && values[0].type === "ident") {
let scale = 0;
if (values[0].string === "cover") {
scale = Math.max(width / imageWidth, height / imageHeight);
} else if (values[0].string === "contain") {
scale = Math.min(width / imageWidth, height / imageHeight);
}
if (scale > 0) {
imageWidth *= scale;
imageHeight *= scale;
res.sizeX = imageWidth;
res.sizeY = imageHeight;
}
}
}
// position
const position = background.position;
if (position) {
const v = parsePosition(position);
if (v) {
const spaceX = width - imageWidth;
const spaceY = height - imageHeight;
if (v.x.unit === "%" && v.y.unit === "%") {
res.posX = spaceX * v.x.value / 100;
res.posY = spaceY * v.y.value / 100;
} else if (v.x.type === "number" && v.y.type === "number" &&
((v.x.unit === "px" && v.y.unit === "px") || (v.x.unit === "" && v.y.unit === ""))) {
res.posX = v.x.value;
res.posY = v.y.value;
} else if (v.x.type === "ident" && v.y.type === "ident") {
if (v.x.string.toLowerCase() === "center") {
res.posX = spaceX / 2;
} else if (v.x.string.toLowerCase() === "right") {
res.posX = spaceX;
}
if (v.y.string.toLowerCase() === "center") {
res.posY = spaceY / 2;
} else if (v.y.string.toLowerCase() === "bottom") {
res.posY = spaceY;
}
}
}
}
return res;
}
function uiColorFromImage(img: UIImage, view: View, callback: (uiColor: UIColor) => void, flip?: boolean): void {
if (!img) {
callback(null);
}
const nativeView = view.nativeView as UIView;
const background = view.style.backgroundInternal;
const frame = nativeView.frame;
const boundsWidth = view.scaleX ? frame.size.width / view.scaleX : frame.size.width;
const boundsHeight = view.scaleY ? frame.size.height / view.scaleY : frame.size.height;
const params = getDrawParams(img, background, boundsWidth, boundsHeight);
if (params.sizeX > 0 && params.sizeY > 0) {
const resizeRect = CGRectMake(0, 0, params.sizeX, params.sizeY);
@ -126,10 +309,10 @@ function getUIColorFromImage(view: View, nativeView: UIView, background: Backgro
if (flip) {
const flippedImage = _flipImage(bkgImage);
return UIColor.alloc().initWithPatternImage(flippedImage);
callback(UIColor.alloc().initWithPatternImage(flippedImage));
} else {
callback(UIColor.alloc().initWithPatternImage(bkgImage));
}
return UIColor.alloc().initWithPatternImage(bkgImage);
}
// Flipping the default coordinate system
@ -158,7 +341,7 @@ function cssValueToDeviceIndependentPixels(source: string, total: number): numbe
}
}
function drawNonUniformBorders(nativeView: NativeView, background: Background) {
function drawNonUniformBorders(nativeView: NativeView, background: BackgroundDefinition) {
const layer = nativeView.layer;
layer.borderColor = undefined;
layer.borderWidth = 0;
@ -284,7 +467,7 @@ function drawNonUniformBorders(nativeView: NativeView, background: Background) {
nativeView.hasNonUniformBorder = hasNonUniformBorder;
}
function drawClipPath(nativeView: UIView, background: Background) {
function drawClipPath(nativeView: UIView, background: BackgroundDefinition) {
const layer = nativeView.layer;
const layerBounds = layer.bounds;
const layerOrigin = layerBounds.origin;

View File

@ -1,12 +1,9 @@
import { Color } from "../../color";
import { Font, parseFont, FontStyle, FontWeight } from "./font";
import { isDataURI, isFileOrResourcePath, layout } from "../../utils/utils";
import { layout } from "../../utils/utils";
import { Background } from "./background";
import { isIOS } from "../../platform";
// TODO: Remove this and start using string as source (for android).
import { fromFileOrResource, fromBase64, fromUrl } from "../../image-source";
import { Style } from "./style";
import { unsetValue, CssProperty, CssAnimationProperty, ShorthandProperty, InheritedCssProperty, makeValidator, makeParser } from "../core/properties";
@ -522,54 +519,11 @@ export const backgroundInternalProperty = new CssProperty<Style, Background>({
});
backgroundInternalProperty.register(Style);
let pattern: RegExp = /url\(('|")(.*?)\1\)/;
// const pattern: RegExp = /url\(('|")(.*?)\1\)/;
export const backgroundImageProperty = new CssProperty<Style, string>({
name: "backgroundImage", cssName: "background-image", valueChanged: (target, oldValue, newValue) => {
let style = target;
let currentBackground = target.backgroundInternal;
let url: string = newValue;
let isValid = false;
if (url === undefined) {
style.backgroundInternal = currentBackground.withImage(undefined);
return;
}
let match = url.match(pattern);
if (match && match[2]) {
url = match[2];
}
if (isDataURI(url)) {
let base64Data = url.split(",")[1];
if (typeof base64Data !== "undefined") {
style.backgroundInternal = currentBackground.withImage(fromBase64(base64Data));
isValid = true;
} else {
style.backgroundInternal = currentBackground.withImage(undefined);
}
}
else if (isFileOrResourcePath(url)) {
style.backgroundInternal = currentBackground.withImage(fromFileOrResource(url));
isValid = true;
}
else if (url.indexOf("http") !== -1) {
style["_url"] = url;
style.backgroundInternal = currentBackground.withImage(undefined);
fromUrl(url).then((r) => {
if (style && style["_url"] === url) {
// Get the current background again, as it might have changed while doing the request.
currentBackground = target.backgroundInternal;
target.backgroundInternal = currentBackground.withImage(r);
}
});
isValid = true;
}
if (!isValid) {
style.backgroundInternal = currentBackground.withImage(undefined);
}
let background = target.backgroundInternal;
target.backgroundInternal = background.withImage(newValue);
}
});
backgroundImageProperty.register(Style);

View File

@ -1,6 +1,7 @@
import * as types from "./types";
export var RESOURCE_PREFIX = "res://";
export const RESOURCE_PREFIX = "res://";
export const FILE_PREFIX = "file:///";
export function escapeRegexSymbols(source: string): string {
let escapeRegex = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g;
@ -116,8 +117,7 @@ export function isDataURI(uri: string): boolean {
return false;
}
var firstSegment = uri.trim().split(',')[0];
const firstSegment = uri.trim().split(',')[0];
return firstSegment && firstSegment.indexOf("data:") === 0 && firstSegment.indexOf('base64') >= 0;
}

View File

@ -2,7 +2,8 @@
* @module "utils/utils"
*/ /** */
export var RESOURCE_PREFIX: string;
export const RESOURCE_PREFIX: string;
export const FILE_PREFIX: string;
//@private
/**

View File

@ -71,7 +71,9 @@
clipPath: string,
backgroundColor: number,
backgroundImage: android.graphics.Bitmap,
backgroundImage: string,
backgroundBitmap: android.graphics.Bitmap,
context: android.content.Context,
backgroundRepeat: string,
backgroundPosition: string,
backgroundPositionParsedCSSValues: native.Array<CSSValue>,
@ -100,7 +102,9 @@
public getClipPath(): string;
public getBackgroundColor(): number;
public getBackgroundImage(): android.graphics.Bitmap;
public getBackgroundImage(): string;
public getBackgroundBitmap(): android.graphics.Bitmap;
public getBackgroundRepeat(): string;
public getBackgroundPosition(): string;
public getBackgroundSize(): string;
@ -401,6 +405,7 @@
public addImageCache(cache: Cache): void;
public initCache(): void;
public clearCache(): void;
public closeCache(): void;
public loadImage(data: Object, imageView: ImageView,
decodeWidth: number, decodeHeight: number, useCache: boolean, async: boolean,
listener: Worker.IOnImageLoadedListener): void;