Files
NativeScript/tns-core-modules/ui/styling/background-common.ts

318 lines
11 KiB
TypeScript

import imageSource = require("image-source");
import colorModule = require("color");
import enums = require("ui/enums");
import definition = require("ui/styling/background");
import cssValue = require("css-value");
import utils = require("utils/utils");
import { isAndroid } from "platform";
import * as typesModule from "utils/types";
var types: typeof typesModule;
function ensureTypes() {
if (!types) {
types = require("utils/types");
}
}
interface CSSValue {
type: string;
string: string;
unit?: string;
value?: number;
}
export class Background implements definition.Background {
public static default = new Background(undefined, undefined, undefined, undefined, undefined, 0, undefined, 0, undefined);
color: colorModule.Color;
image: imageSource.ImageSource;
repeat: string;
position: string;
size: string;
// The ones below are used on Android only
borderWidth: number = 0;
borderColor: colorModule.Color;
borderRadius: number = 0;
clipPath: string;
constructor(
color: colorModule.Color,
image: imageSource.ImageSource,
repeat: string,
position: string,
size: string,
borderWidth: number,
borderColor: colorModule.Color,
borderRadius: number,
clipPath: string
) {
this.color = color;
this.image = image;
this.repeat = repeat;
this.position = position;
this.size = size;
this.borderWidth = borderWidth;
this.borderColor = borderColor;
this.borderRadius = borderRadius;
this.clipPath = clipPath;
}
public withColor(value: colorModule.Color): Background {
return new Background(value, this.image, this.repeat, this.position, this.size, this.borderWidth, this.borderColor, this.borderRadius, this.clipPath);
}
public withImage(value: imageSource.ImageSource): Background {
return new Background(this.color, value, this.repeat, this.position, this.size, this.borderWidth, this.borderColor, this.borderRadius, this.clipPath);
}
public withRepeat(value: string): Background {
return new Background(this.color, this.image, value, this.position, this.size, this.borderWidth, this.borderColor, this.borderRadius, this.clipPath);
}
public withPosition(value: string): Background {
return new Background(this.color, this.image, this.repeat, value, this.size, this.borderWidth, this.borderColor, this.borderRadius, this.clipPath);
}
public withSize(value: string): Background {
return new Background(this.color, this.image, this.repeat, this.position, value, this.borderWidth, this.borderColor, this.borderRadius, this.clipPath);
}
public withBorderWidth(value: number): Background {
return new Background(this.color, this.image, this.repeat, this.position, this.size, value, this.borderColor, this.borderRadius, this.clipPath);
}
public withBorderColor(value: colorModule.Color): Background {
return new Background(this.color, this.image, this.repeat, this.position, this.size, this.borderWidth, value, this.borderRadius, this.clipPath);
}
public withBorderRadius(value: number): Background {
return new Background(this.color, this.image, this.repeat, this.position, this.size, this.borderWidth, this.borderColor, value, this.clipPath);
}
public withClipPath(value: string): Background {
return new Background(this.color, this.image, this.repeat, this.position, this.size, this.borderWidth, this.borderColor, this.borderRadius, value);
}
public getDrawParams(width: number, height: number): definition.BackgroundDrawParams {
if (!this.image) {
return null;
}
var res: definition.BackgroundDrawParams = {
repeatX: true,
repeatY: true,
posX: 0,
posY: 0,
}
// repeat
if (this.repeat) {
switch (this.repeat.toLowerCase()) {
case enums.BackgroundRepeat.noRepeat:
res.repeatX = false;
res.repeatY = false;
break;
case enums.BackgroundRepeat.repeatX:
res.repeatY = false;
break;
case enums.BackgroundRepeat.repeatY:
res.repeatX = false;
break;
}
}
var imageWidth = this.image.width;
var imageHeight = this.image.height;
// size
if (this.size) {
let values = cssValue(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 = cssValue(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 {
ensureTypes();
if (isAndroid){
return types.isNullOrUndefined(this.image)
&& types.isNullOrUndefined(this.color)
&& !this.borderWidth
&& !this.borderRadius
&& !this.clipPath;
}
else {
return types.isNullOrUndefined(this.image)
&& types.isNullOrUndefined(this.color);
}
}
public static equals(value1: Background, value2: Background): boolean {
// both values are falsy
if (!value1 && !value2) {
return true;
}
// only one is falsy
if (!value1 || !value2) {
return false;
}
if (isAndroid){
return value1.image === value2.image
&& value1.position === value2.position
&& value1.repeat === value2.repeat
&& value1.size === value2.size
&& colorModule.Color.equals(value1.color, value2.color)
&& value1.borderWidth === value2.borderWidth
&& colorModule.Color.equals(value1.borderColor, value2.borderColor)
&& value1.borderRadius === value2.borderRadius
&& value1.clipPath === value2.clipPath;
}
else {
return value1.image === value2.image
&& value1.position === value2.position
&& value1.repeat === value2.repeat
&& value1.size === value2.size
&& colorModule.Color.equals(value1.color, value2.color);
}
}
public toString(): string {
return `isEmpty: ${this.isEmpty()}; color: ${this.color}; image: ${this.image}; repeat: ${this.repeat}; position: ${this.position}; size: ${this.size}; borderWidth: ${this.borderWidth}; borderColor: ${this.borderColor}; borderRadius: ${this.borderRadius}; clipPath: ${this.clipPath};`;
}
}
export function cssValueToDevicePixels(source: string, total: number): number {
var result;
source = source.trim();
if (source.indexOf("px") !== -1) {
result = parseFloat(source.replace("px", ""));
}
else if (source.indexOf("%") !== -1 && total > 0) {
result = (parseFloat(source.replace("%", "")) / 100) * utils.layout.toDeviceIndependentPixels(total);
} else {
result = parseFloat(source);
}
return utils.layout.toDevicePixels(result);
}