Implemented percent support for width, height and margins

This commit is contained in:
Hristo Hristov
2015-12-15 14:36:04 +02:00
parent 9418ab8f63
commit 1dbb742908
25 changed files with 834 additions and 212 deletions

10
ui/styling/style.d.ts vendored
View File

@@ -18,11 +18,19 @@ declare module "ui/styling/style" {
width: number;
height: number;
widthPercent: number;
heightPercent: number;
leftMargin: number;
topMargin: number;
rightMargin: number;
bottomMargin: number;
leftMarginPercent: number;
topMarginPercent: number;
rightMarginPercent: number;
bottomMarginPercent: number;
horizontalAlignment: string;
verticalAlignment: string;
}
@@ -66,7 +74,7 @@ declare module "ui/styling/style" {
public opacity: number;
public whiteSpace: string;
new(parentView: View);
constructor(parentView: View);
public _beginUpdate();
public _endUpdate();

View File

@@ -13,6 +13,7 @@ import utils = require("utils/utils");
import font = require("ui/styling/font");
import background = require("ui/styling/background");
import platform = require("platform");
import definition = require("ui/styling/style");
// key is the property id and value is Dictionary<string, StylePropertyChangedHandler>;
var _registeredHandlers = Array<Object>();
@@ -26,28 +27,67 @@ var noStylingClasses = {};
// on Android we explicitly set propertySettings to None because android will invalidate its layout (skip unnecessary native call).
var AffectsLayout = platform.device.os === platform.platformNames.android ? PropertyMetadataSettings.None : PropertyMetadataSettings.AffectsLayout;
export interface Thickness {
left: number;
top: number;
right: number;
bottom: number;
interface ThicknessValue {
left: Object;
top: Object;
right: Object;
bottom: Object;
}
export interface CommonLayoutParams {
width: number;
height: number;
leftMargin: number;
topMargin: number;
rightMargin: number;
bottomMargin: number;
horizontalAlignment: string;
verticalAlignment: string;
interface PercentHelper {
value: number;
isPercent: boolean;
isError: boolean;
}
function parseThickness(value: any): Thickness {
var result: Thickness = { top: 0, right: 0, bottom: 0, left: 0 };
function parseMargin(value: any): ThicknessValue {
if (types.isString(value)) {
let arr = (<string>value).split(/[ ,]+/);
let top: Object;
let right: Object;
let bottom: Object;
let left: Object;
if (arr.length === 1) {
top = right = bottom = left = arr[0];
}
else if (arr.length === 2) {
top = bottom = arr[0];
right = left = arr[1];
}
else if (arr.length === 4) {
top = arr[0];
right = arr[1];
bottom = arr[2];
left = arr[3];
}
else {
throw new Error("Invalid value for margin: " + value);
}
return {
top: top,
right: right,
bottom: bottom,
left: left
}
}
else if (types.isNumber(value)) {
return {
top: value,
right: value,
bottom: value,
left: value
}
}
else {
return value;
}
}
function parseThickness(value: any): definition.Thickness {
var result: definition.Thickness = { top: 0, right: 0, bottom: 0, left: 0 };
if (types.isString(value)) {
var arr = value.split(/[ ,]+/);
var top = parseInt(arr[0]);
@@ -77,7 +117,7 @@ function parseThickness(value: any): Thickness {
return result;
}
function layoutParamsComparer(x: CommonLayoutParams, y: CommonLayoutParams): boolean {
function layoutParamsComparer(x: definition.CommonLayoutParams, y: definition.CommonLayoutParams): boolean {
return x.width === y.width
&& x.height === y.height
&& x.leftMargin === y.leftMargin
@@ -86,17 +126,61 @@ function layoutParamsComparer(x: CommonLayoutParams, y: CommonLayoutParams): boo
&& x.bottomMargin === y.bottomMargin
&& x.horizontalAlignment === y.horizontalAlignment
&& x.verticalAlignment === y.verticalAlignment
&& x.widthPercent === y.widthPercent
&& x.heightPercent === y.heightPercent
&& x.leftMarginPercent === y.leftMarginPercent
&& x.topMarginPercent === y.topMarginPercent
&& x.rightMarginPercent === y.rightMarginPercent
&& x.bottomMarginPercent === y.bottomMarginPercent
}
function onLayoutParamsChanged(data: PropertyChangeData) {
var style = <Style>data.object;
var layoutParams: CommonLayoutParams = {
width: isNaN(style.width) ? -1 : style.width,
height: isNaN(style.height) ? -1 : style.height,
leftMargin: style.marginLeft,
topMargin: style.marginTop,
rightMargin: style.marginRight,
bottomMargin: style.marginBottom,
let style = <Style>data.object;
let widthValue = convertToPercentHelper(style.width);
let width: number;
let widthPercent: number;
if (widthValue.isPercent) {
width = style.horizontalAlignment === enums.HorizontalAlignment.stretch ? -1 : -2;
widthPercent = widthValue.value / 100;
}
else {
width = isNaN(widthValue.value) ? -1 : widthValue.value;
widthPercent = -1;
}
let heightValue = convertToPercentHelper(style.height);
let height: number;
let heightPercent: number;
if (heightValue.isPercent) {
height = style.verticalAlignment === enums.VerticalAlignment.stretch ? -1 : -2;
heightPercent = heightValue.value / 100;
}
else {
height = isNaN(heightValue.value) ? -1 : heightValue.value;
heightPercent = -1;
}
let marginLeftValue = convertToPercentHelper(style.marginLeft);
let marginTopValue = convertToPercentHelper(style.marginTop);
let marginRightValue = convertToPercentHelper(style.marginRight);
let marginBottomValue = convertToPercentHelper(style.marginBottom);
// Negative marginPercent means no marginPercent so native layout won't override margin with this % value.
var layoutParams: definition.CommonLayoutParams =
{
width: width,
height: height,
widthPercent: widthPercent,
heightPercent: heightPercent,
leftMargin: marginLeftValue.isPercent ? 0 : marginLeftValue.value,
leftMarginPercent: marginLeftValue.isPercent ? marginLeftValue.value / 100 : -1,
topMargin: marginTopValue.isPercent ? 0 : marginTopValue.value,
topMarginPercent: marginTopValue.isPercent ? marginTopValue.value / 100 : -1,
rightMargin: marginRightValue.isPercent ? 0 : marginRightValue.value,
rightMarginPercent: marginRightValue.isPercent ? marginRightValue.value / 100 : -1,
bottomMargin: marginBottomValue.isPercent ? 0 : marginBottomValue.value,
bottomMarginPercent: marginBottomValue.isPercent ? marginBottomValue.value / 100 : -1,
horizontalAlignment: style.horizontalAlignment,
verticalAlignment: style.verticalAlignment
};
@@ -106,7 +190,7 @@ function onLayoutParamsChanged(data: PropertyChangeData) {
function onPaddingValueChanged(data: PropertyChangeData) {
var style = <Style>data.object;
var thickness: Thickness = {
var thickness: definition.Thickness = {
top: style.paddingTop,
right: style.paddingRight,
bottom: style.paddingBottom,
@@ -116,15 +200,66 @@ function onPaddingValueChanged(data: PropertyChangeData) {
style._setValue(nativePaddingsProperty, thickness);
}
function thicknessComparer(x: Thickness, y: Thickness): boolean {
function thicknessComparer(x: definition.Thickness, y: definition.Thickness): boolean {
if (x && y) {
return x.left === y.left && x.top === y.top && x.right === y.right && x.bottom === y.bottom;
}
return !x === !y;
}
function isWidthHeightValid(value: number): boolean {
return isNaN(value) || (value >= 0.0 && isFinite(value));
function convertToPercentHelper(value: Object): PercentHelper {
let numberValue = 0;
let isPercent = false;
let isError = true;
if (types.isString(value)) {
var stringValue = (<string>value).trim();
var percentIndex = stringValue.indexOf("%");
if (percentIndex !== -1) {
// if only % or % is not last we treat it as invalid value.
if (percentIndex !== (stringValue.length - 1) || percentIndex === 0) {
numberValue = 0;
}
else {
isPercent = true;
numberValue = converters.numberConverter(stringValue.substring(0, stringValue.length - 1).trim());
isError = numberValue === 0;
}
}
else {
isError = false;
isPercent = false;
numberValue = converters.numberConverter(stringValue);
}
}
else if (types.isNumber(value)) {
isError = false;
isPercent = false;
numberValue = <number>value;
}
return {
isError: isError,
isPercent: isPercent,
value: numberValue
}
}
function numberOrPercentConverter(value: Object) {
let result = convertToPercentHelper(value);
if (result.isError) {
throw new Error("Invalid value: " + value);
}
return result.isPercent ? value : result.value;
}
function isWidthHeightValid(value: Object): boolean {
var result = convertToPercentHelper(value);
if (result.isError) {
return false;
}
return isNaN(result.value) || (result.value >= 0.0 && isFinite(result.value));
}
function isMinWidthHeightValid(value: number): boolean {
@@ -253,16 +388,17 @@ function isWhiteSpaceValid(value: string): boolean {
return value === enums.WhiteSpace.nowrap || value === enums.WhiteSpace.normal;
}
function onVisibilityChanged(data: PropertyChangeData) {
(<any>data.object)._view._isVisibleCache = data.newValue === enums.Visibility.visible;
}
function isPaddingValid(value: number): boolean {
return isFinite(value) && !isNaN(value) && value >= 0;
}
function isMarginValid(value: number): boolean {
return isFinite(value) && !isNaN(value);
var result = convertToPercentHelper(value);
if (result.isError) {
return false;
}
return isFinite(result.value) && !isNaN(result.value);
}
function isOpacityValid(value: string): boolean {
@@ -278,6 +414,10 @@ function isFontStyleValid(value: string): boolean {
return value === enums.FontStyle.normal || value === enums.FontStyle.italic;
}
function onVisibilityChanged(data: PropertyChangeData) {
(<any>data.object)._view._isVisibleCache = data.newValue === enums.Visibility.visible;
}
function onFontFamilyChanged(data: PropertyChangeData) {
var style = <Style>data.object;
@@ -413,7 +553,7 @@ export class Style extends DependencyObservable implements styling.Style {
get font(): string {
return this.fontStyle + " " + this.fontWeight + " " + this.fontSize + " " + this.fontFamily;
}
set font(value: string) {
set font(value: string) {
this._setShorthandProperty("font", value);
}
@@ -678,7 +818,7 @@ export class Style extends DependencyObservable implements styling.Style {
}
private _applyStyleProperty(property: Property, newValue: any) {
if (!this._view._shouldApplyStyleHandlers()) {
return;
}
@@ -856,20 +996,38 @@ export var whiteSpaceProperty = new styleProperty.Property("whiteSpace", "white-
export var nativeLayoutParamsProperty = new styleProperty.Property("nativeLayoutParams", "nativeLayoutParams",
new PropertyMetadata({
width: -1,
widthPercent: -1,
height: -1,
heightPercent: -1,
leftMargin: 0,
leftMarginPercent: -1,
topMargin: 0,
topMarginPercent: -1,
rightMargin: 0,
rightMarginPercent: -1,
bottomMargin: 0,
bottomMarginPercent: -1,
horizontalAlignment: enums.HorizontalAlignment.stretch,
verticalAlignment: enums.VerticalAlignment.stretch
}, null, null, null, layoutParamsComparer));
export var widthProperty = new styleProperty.Property("width", "width",
new PropertyMetadata(Number.NaN, AffectsLayout, onLayoutParamsChanged, isWidthHeightValid), converters.numberConverter);
new PropertyMetadata(Number.NaN, AffectsLayout, onLayoutParamsChanged, isWidthHeightValid), numberOrPercentConverter);
export var heightProperty = new styleProperty.Property("height", "height",
new PropertyMetadata(Number.NaN, AffectsLayout, onLayoutParamsChanged, isWidthHeightValid), converters.numberConverter);
new PropertyMetadata(Number.NaN, AffectsLayout, onLayoutParamsChanged, isWidthHeightValid), numberOrPercentConverter);
export var marginLeftProperty = new styleProperty.Property("marginLeft", "margin-left",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), numberOrPercentConverter);
export var marginRightProperty = new styleProperty.Property("marginRight", "margin-right",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), numberOrPercentConverter);
export var marginTopProperty = new styleProperty.Property("marginTop", "margin-top",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), numberOrPercentConverter);
export var marginBottomProperty = new styleProperty.Property("marginBottom", "margin-bottom",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), numberOrPercentConverter);
export var verticalAlignmentProperty = new styleProperty.Property("verticalAlignment", "vertical-align",
new PropertyMetadata(enums.VerticalAlignment.stretch, AffectsLayout, onLayoutParamsChanged));
@@ -877,18 +1035,6 @@ export var verticalAlignmentProperty = new styleProperty.Property("verticalAlign
export var horizontalAlignmentProperty = new styleProperty.Property("horizontalAlignment", "horizontal-align",
new PropertyMetadata(enums.HorizontalAlignment.stretch, AffectsLayout, onLayoutParamsChanged));
export var marginLeftProperty = new styleProperty.Property("marginLeft", "margin-left",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), converters.numberConverter);
export var marginRightProperty = new styleProperty.Property("marginRight", "margin-right",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), converters.numberConverter);
export var marginTopProperty = new styleProperty.Property("marginTop", "margin-top",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), converters.numberConverter);
export var marginBottomProperty = new styleProperty.Property("marginBottom", "margin-bottom",
new PropertyMetadata(0, AffectsLayout, onLayoutParamsChanged, isMarginValid), converters.numberConverter);
function getNativePadding(nativeView: android.view.View, callback: (view: android.view.View) => number): NativeValueResult {
return {
result: nativeView ? callback(nativeView) / utils.layout.getDisplayDensity() : 0,
@@ -954,7 +1100,7 @@ function onPaddingChanged(value: any): Array<styleProperty.KeyValuePair<stylePro
}
function onMarginChanged(value: any): Array<styleProperty.KeyValuePair<styleProperty.Property, any>> {
var thickness = parseThickness(value);
var thickness = parseMargin(value);
var array = new Array<styleProperty.KeyValuePair<styleProperty.Property, any>>();
array.push({ property: marginTopProperty, value: thickness.top });
array.push({ property: marginRightProperty, value: thickness.right });

View File

@@ -9,6 +9,7 @@ import styleModule = require("./style");
import font = require("ui/styling/font");
import background = require("ui/styling/background");
import tabView = require("ui/tab-view");
import {CommonLayoutParams, Thickness} from "ui/styling/style";
var btn;
global.moduleMerge(stylersCommon, exports);
@@ -148,10 +149,18 @@ export class DefaultStyler implements definition.stylers.Styler {
return lp;
}
private static setNativeLayoutParamsProperty(view: view.View, params: style.CommonLayoutParams): void {
private static setNativeLayoutParamsProperty(view: view.View, params: CommonLayoutParams): void {
var nativeView: android.view.View = view._nativeView;
var lp = DefaultStyler.getNativeLayoutParams(nativeView);
lp.widthPercent = params.widthPercent;
lp.heightPercent = params.heightPercent;
lp.leftMarginPercent = params.leftMarginPercent;
lp.topMarginPercent = params.topMarginPercent;
lp.rightMarginPercent = params.rightMarginPercent;
lp.bottomMarginPercent = params.bottomMarginPercent;
lp.leftMargin = Math.round(params.leftMargin * utils.layout.getDisplayDensity());
lp.topMargin = Math.round(params.topMargin * utils.layout.getDisplayDensity());
lp.rightMargin = Math.round(params.rightMargin * utils.layout.getDisplayDensity());
@@ -242,7 +251,7 @@ export class DefaultStyler implements definition.stylers.Styler {
nativeView.setLayoutParams(lp);
}
private static setPaddingProperty(view: view.View, newValue: style.Thickness) {
private static setPaddingProperty(view: view.View, newValue: Thickness) {
var density = utils.layout.getDisplayDensity();
var left = Math.round((newValue.left + view.borderWidth) * density);
var top = Math.round((newValue.top + view.borderWidth) * density);
@@ -251,7 +260,7 @@ export class DefaultStyler implements definition.stylers.Styler {
(<android.view.View>view._nativeView).setPadding(left, top, right, bottom);
}
private static resetPaddingProperty(view: view.View, nativeValue: style.Thickness) {
private static resetPaddingProperty(view: view.View, nativeValue: Thickness) {
var density = utils.layout.getDisplayDensity();
var left = Math.round((nativeValue.left + view.borderWidth) * density);
var top = Math.round((nativeValue.top + view.borderWidth) * density);
@@ -395,8 +404,10 @@ export class TextViewStyler implements definition.stylers.Styler {
private static resetFontInternalProperty(view: view.View, nativeValue: any) {
var tv: android.widget.TextView = <android.widget.TextView>view._nativeView;
tv.setTypeface(nativeValue.typeface);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, nativeValue.size);
if (tv && nativeValue) {
tv.setTypeface(nativeValue.typeface);
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, nativeValue.size);
}
}
private static getNativeFontInternalValue(view: view.View): any {