mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 03:31:45 +08:00
517 lines
20 KiB
TypeScript
517 lines
20 KiB
TypeScript
import styling = require("ui/styling");
|
|
import observable = require("ui/core/dependency-observable");
|
|
import color = require("color");
|
|
import types = require("utils/types");
|
|
import trace = require("trace");
|
|
import dependencyObservable = require("ui/core/dependency-observable");
|
|
import view = require("ui/core/view");
|
|
import stylers = require("ui/styling/stylers");
|
|
import styleProperty = require("ui/styling/style-property");
|
|
import converters = require("ui/styling/converters");
|
|
import enums = require("ui/enums");
|
|
import imageSource = require("image-source");
|
|
|
|
// key is the property id and value is Dictionary<string, StylePropertyChangedHandler>;
|
|
var _registeredHandlers = {};
|
|
// key is a className + property id and value is StylePropertyChangedHandler;
|
|
var _handlersCache = {};
|
|
// classes like Frame that does not need to handle styling properties.
|
|
var noStylingClasses = {};
|
|
|
|
export class Style extends observable.DependencyObservable implements styling.Style {
|
|
private _view: view.View;
|
|
|
|
get color(): color.Color {
|
|
return this._getValue(colorProperty);
|
|
}
|
|
set color(value: color.Color) {
|
|
this._setValue(colorProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get backgroundColor(): color.Color {
|
|
return this._getValue(backgroundColorProperty);
|
|
}
|
|
set backgroundColor(value: color.Color) {
|
|
this._setValue(backgroundColorProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get backgroundImage(): string {
|
|
return this._getValue(backgroundImageProperty);
|
|
}
|
|
set backgroundImage(value: string) {
|
|
this._setValue(backgroundImageProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get fontSize(): number {
|
|
return this._getValue(fontSizeProperty);
|
|
}
|
|
set fontSize(value: number) {
|
|
this._setValue(fontSizeProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get textAlignment(): string {
|
|
return this._getValue(textAlignmentProperty);
|
|
}
|
|
set textAlignment(value: string) {
|
|
this._setValue(textAlignmentProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get minWidth(): number {
|
|
return this._getValue(minWidthProperty);
|
|
}
|
|
set minWidth(value: number) {
|
|
this._setValue(minWidthProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get minHeight(): number {
|
|
return this._getValue(minHeightProperty);
|
|
}
|
|
set minHeight(value: number) {
|
|
this._setValue(minHeightProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get width(): number {
|
|
return this._getValue(widthProperty);
|
|
}
|
|
set width(value: number) {
|
|
this._setValue(widthProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get height(): number {
|
|
return this._getValue(heightProperty);
|
|
}
|
|
set height(value: number) {
|
|
this._setValue(heightProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get margin(): string {
|
|
return this._getValue(marginProperty);
|
|
}
|
|
set margin(value: string) {
|
|
this._setValue(marginProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get marginLeft(): number {
|
|
return this._getValue(marginLeftProperty);
|
|
}
|
|
set marginLeft(value: number) {
|
|
this._setValue(marginLeftProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get marginTop(): number {
|
|
return this._getValue(marginTopProperty);
|
|
}
|
|
set marginTop(value: number) {
|
|
this._setValue(marginTopProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get marginRight(): number {
|
|
return this._getValue(marginRightProperty);
|
|
}
|
|
set marginRight(value: number) {
|
|
this._setValue(marginRightProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get marginBottom(): number {
|
|
return this._getValue(marginBottomProperty);
|
|
}
|
|
set marginBottom(value: number) {
|
|
this._setValue(marginBottomProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get padding(): string {
|
|
return this._getValue(paddingProperty);
|
|
}
|
|
set padding(value: string) {
|
|
this._setValue(paddingProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get paddingLeft(): number {
|
|
return this._getValue(paddingLeftProperty);
|
|
}
|
|
set paddingLeft(value: number) {
|
|
this._setValue(paddingLeftProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get paddingTop(): number {
|
|
return this._getValue(paddingTopProperty);
|
|
}
|
|
set paddingTop(value: number) {
|
|
this._setValue(paddingTopProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get paddingRight(): number {
|
|
return this._getValue(paddingRightProperty);
|
|
}
|
|
set paddingRight(value: number) {
|
|
this._setValue(paddingRightProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get paddingBottom(): number {
|
|
return this._getValue(paddingBottomProperty);
|
|
}
|
|
set paddingBottom(value: number) {
|
|
this._setValue(paddingBottomProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get horizontalAlignment(): string {
|
|
return this._getValue(horizontalAlignmentProperty);
|
|
}
|
|
set horizontalAlignment(value: string) {
|
|
this._setValue(horizontalAlignmentProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get verticalAlignment(): string {
|
|
return this._getValue(verticalAlignmentProperty);
|
|
}
|
|
set verticalAlignment(value: string) {
|
|
this._setValue(verticalAlignmentProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get visibility(): string {
|
|
return this._getValue(visibilityProperty);
|
|
}
|
|
set visibility(value: string) {
|
|
this._setValue(visibilityProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
get opacity(): number {
|
|
return this._getValue(opacityProperty);
|
|
}
|
|
set opacity(value: number) {
|
|
this._setValue(opacityProperty, value, observable.ValueSource.Local);
|
|
}
|
|
|
|
constructor(parentView: view.View) {
|
|
super();
|
|
this._view = parentView;
|
|
}
|
|
|
|
public _resetCssValues() {
|
|
var that = this;
|
|
this._eachSetProperty(function (property: observable.Property) {
|
|
that._resetValue(property, observable.ValueSource.Css);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
public _onPropertyChanged(property: dependencyObservable.Property, oldValue: any, newValue: any) {
|
|
trace.write(
|
|
"Style._onPropertyChanged view:" + this._view +
|
|
", property: " + property.name +
|
|
", oldValue: " + oldValue +
|
|
", newValue: " + newValue, trace.categories.Style);
|
|
|
|
super._onPropertyChanged(property, oldValue, newValue);
|
|
|
|
this._view._checkMetadataOnPropertyChanged(property.metadata);
|
|
|
|
this._applyProperty(property, newValue);
|
|
}
|
|
|
|
public _syncNativeProperties() {
|
|
var that = this;
|
|
// loop all style properties and call the _applyProperty method
|
|
// TODO: Potential performance bottle-neck
|
|
styleProperty.eachProperty(function (p: styleProperty.Property) {
|
|
var value = that._getValue(p);
|
|
if (types.isDefined(value)) {
|
|
that._applyProperty(p, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
private _applyProperty(property: dependencyObservable.Property, newValue: any) {
|
|
this._applyStyleProperty(property, newValue);
|
|
|
|
// The effective value of an inheritable property has changed
|
|
// propagate the change down to the descendants to update their inherited properties.
|
|
if (this._view._childrenCount === 0 || !property.metadata.inheritable) {
|
|
return;
|
|
}
|
|
|
|
var eachChild = function (child: view.View): boolean {
|
|
child.style._inheritStyleProperty(property);
|
|
return true;
|
|
}
|
|
|
|
this._view._eachChildView(eachChild);
|
|
}
|
|
|
|
private _applyStyleProperty(property: dependencyObservable.Property, newValue: any) {
|
|
try {
|
|
var handler: styling.stylers.StylePropertyChangedHandler = getHandler(property, this._view);
|
|
|
|
if (!handler) {
|
|
trace.write("No handler for property: " + property.name + " with id: " + property.id + ", view:" + view, trace.categories.Style);
|
|
}
|
|
else {
|
|
trace.write("Found handler for property: " + property.name + ", view:" + this._view, trace.categories.Style);
|
|
|
|
if (types.isUndefined(newValue)) {
|
|
(<any>handler).resetProperty(property, this._view);
|
|
} else {
|
|
(<any>handler).applyProperty(property, this._view, newValue);
|
|
}
|
|
}
|
|
}
|
|
catch (ex) {
|
|
trace.write("Error setting property: " + property.name + " on " + this._view + ": " + ex, trace.categories.Style, trace.messageType.error);
|
|
}
|
|
}
|
|
|
|
public _inheritStyleProperty(property: dependencyObservable.Property) {
|
|
if (!property.metadata.inheritable) {
|
|
throw new Error("An attempt was made to inherit a style property which is not marked as 'inheritable'.");
|
|
}
|
|
|
|
var currentParent = this._view.parent;
|
|
var valueSource: number;
|
|
|
|
while (currentParent) {
|
|
valueSource = currentParent.style._getValueSource(property);
|
|
if (valueSource > dependencyObservable.ValueSource.Default) {
|
|
this._setValue(property, currentParent.style._getValue(property), dependencyObservable.ValueSource.Inherited);
|
|
break;
|
|
}
|
|
|
|
currentParent = currentParent.parent;
|
|
}
|
|
}
|
|
|
|
public _inheritStyleProperties() {
|
|
var that = this;
|
|
styleProperty.eachInheritableProperty((p) => {
|
|
that._inheritStyleProperty(p);
|
|
});
|
|
}
|
|
}
|
|
|
|
export function registerHandler(property: dependencyObservable.Property,
|
|
handler: styling.stylers.StylePropertyChangedHandler,
|
|
className?: string) {
|
|
var realClassName = className ? className : "default";
|
|
if (_registeredHandlers.hasOwnProperty(property.id + "")) {
|
|
_registeredHandlers[property.id][realClassName] = handler;
|
|
}
|
|
else {
|
|
var handlerRecord = {};
|
|
handlerRecord[realClassName] = handler;
|
|
_registeredHandlers[property.id] = handlerRecord;
|
|
}
|
|
}
|
|
|
|
export function registerNoStylingClass(className) {
|
|
noStylingClasses[className] = 1;
|
|
}
|
|
|
|
export function getHandler(property: dependencyObservable.Property, view: view.View): styling.stylers.StylePropertyChangedHandler {
|
|
var classNames = types.getBaseClasses(view);
|
|
// adding default as last class name if no other class is found default handler will be used
|
|
classNames.push("default");
|
|
if (_handlersCache.hasOwnProperty(classNames[0] + property.id)) {
|
|
return _handlersCache[classNames[0] + property.id];
|
|
}
|
|
else {
|
|
var i;
|
|
var propertyHandlers;
|
|
var handler;
|
|
propertyHandlers = _registeredHandlers[property.id];
|
|
for (i = 0; i < classNames.length; i++) {
|
|
if (propertyHandlers) {
|
|
var loopClassName = classNames[i];
|
|
if (noStylingClasses.hasOwnProperty(loopClassName)) {
|
|
_handlersCache[loopClassName + property.id] = null;
|
|
return null;
|
|
}
|
|
if (propertyHandlers.hasOwnProperty(loopClassName)) {
|
|
handler = propertyHandlers[loopClassName];
|
|
_handlersCache[loopClassName + property.id] = handler;
|
|
return handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Property registration
|
|
export var colorProperty = new styleProperty.Property("color", "color",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.Inheritable, undefined, undefined, color.Color.equals),
|
|
converters.colorConverter);
|
|
|
|
export var backgroundImageProperty = new styleProperty.Property("backgroundImage", "background-image",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.None, onBackgroundImagePropertyChanged));
|
|
|
|
function onBackgroundImagePropertyChanged(data: observable.PropertyChangeData) {
|
|
var style = <Style>data.object;
|
|
var pattern: RegExp = /url\(('|")(.*?)\1\)/;
|
|
var url = (<string>data.newValue).match(pattern)[2];
|
|
|
|
if (imageSource.isFileOrResourcePath(url)) {
|
|
style._setValue(backgroundImageSourceProperty, imageSource.fromFileOrResource(url), observable.ValueSource.Local);
|
|
} else if (types.isString(url)) {
|
|
imageSource.fromUrl(url).then(r=> {
|
|
style._setValue(backgroundImageSourceProperty, r, observable.ValueSource.Local);
|
|
});
|
|
}
|
|
}
|
|
|
|
export var backgroundImageSourceProperty = new styleProperty.Property("backgroundImageSource", "background-image-source",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.None, undefined, undefined, undefined));
|
|
|
|
export var backgroundColorProperty = new styleProperty.Property("backgroundColor", "background-color",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.None, undefined, undefined, color.Color.equals),
|
|
converters.colorConverter);
|
|
|
|
export var fontSizeProperty = new styleProperty.Property("fontSize", "font-size",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.AffectsLayout | observable.PropertyMetadataSettings.Inheritable),
|
|
converters.fontSizeConverter);
|
|
|
|
export var textAlignmentProperty = new styleProperty.Property("textAlignment", "text-align",
|
|
new observable.PropertyMetadata(undefined, observable.PropertyMetadataSettings.AffectsLayout | observable.PropertyMetadataSettings.Inheritable),
|
|
converters.textAlignConverter);
|
|
|
|
function isWidthHeightValid(value: number): boolean {
|
|
return isNaN(value) || (value >= 0.0 && isFinite(value));
|
|
}
|
|
|
|
function isMinWidthHeightValid(value: number): boolean {
|
|
return !isNaN(value) && value >= 0.0 && isFinite(value);
|
|
}
|
|
|
|
export var widthProperty = new styleProperty.Property("width", "width",
|
|
new observable.PropertyMetadata(
|
|
Number.NaN, observable.PropertyMetadataSettings.AffectsLayout, null, isWidthHeightValid),
|
|
converters.numberConverter);
|
|
|
|
export var heightProperty = new styleProperty.Property("height", "height",
|
|
new observable.PropertyMetadata(
|
|
Number.NaN, observable.PropertyMetadataSettings.AffectsLayout, null, isWidthHeightValid),
|
|
converters.numberConverter);
|
|
|
|
export var minWidthProperty = new styleProperty.Property("minWidth", "min-width",
|
|
new observable.PropertyMetadata(
|
|
0, observable.PropertyMetadataSettings.AffectsLayout, null, isMinWidthHeightValid),
|
|
converters.numberConverter);
|
|
|
|
export var minHeightProperty = new styleProperty.Property("minHeight", "min-height",
|
|
new observable.PropertyMetadata(
|
|
0, observable.PropertyMetadataSettings.AffectsLayout, null, isMinWidthHeightValid),
|
|
converters.numberConverter);
|
|
|
|
function parseThickness(value: any): { top: number; right: number; bottom: number; left: number } {
|
|
var result = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
if (types.isString(value)) {
|
|
var arr = value.split(/[ ,]+/);
|
|
var top = parseInt(arr[0]);
|
|
top = isNaN(top) ? 0 : top;
|
|
|
|
var right = parseInt(arr[1]);
|
|
right = isNaN(right) ? top : right;
|
|
|
|
var bottom = parseInt(arr[2]);
|
|
bottom = isNaN(bottom) ? top : bottom;
|
|
|
|
var left = parseInt(arr[3]);
|
|
left = isNaN(left) ? right : left;
|
|
|
|
result.top = top;
|
|
result.right = right;
|
|
result.bottom = bottom;
|
|
result.left = left;
|
|
|
|
} else if (types.isNumber(value)) {
|
|
result.top = result.right = result.bottom = result.left = value;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function onPaddingChanged(data: observable.PropertyChangeData) {
|
|
var thickness = parseThickness(data.newValue);
|
|
var style = <Style>data.object;
|
|
|
|
style.paddingTop = thickness.top;
|
|
style.paddingRight = thickness.right;
|
|
style.paddingBottom = thickness.bottom;
|
|
style.paddingLeft = thickness.left;
|
|
}
|
|
|
|
function onMarginChanged(data: observable.PropertyChangeData) {
|
|
var thickness = parseThickness(data.newValue);
|
|
var style = <Style>data.object;
|
|
|
|
style.marginTop = thickness.top;
|
|
style.marginRight = thickness.right;
|
|
style.marginBottom = thickness.bottom;
|
|
style.marginLeft = thickness.left;
|
|
}
|
|
|
|
export var verticalAlignmentProperty = new styleProperty.Property("verticalAlignment", "vertical-align",
|
|
new observable.PropertyMetadata(enums.VerticalAlignment.stretch, observable.PropertyMetadataSettings.AffectsLayout));
|
|
|
|
export var horizontalAlignmentProperty = new styleProperty.Property("horizontalAlignment", "horizontal-align",
|
|
new observable.PropertyMetadata(enums.HorizontalAlignment.stretch, observable.PropertyMetadataSettings.AffectsLayout));
|
|
|
|
export var marginProperty = new styleProperty.Property("margin", "margin",
|
|
new observable.PropertyMetadata(null, null, onMarginChanged));
|
|
|
|
export var paddingProperty = new styleProperty.Property("padding", "padding",
|
|
new observable.PropertyMetadata(null, null, onPaddingChanged));
|
|
|
|
export var marginLeftProperty = new styleProperty.Property("marginLeft", "margin-left",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isMarginValid), converters.numberConverter);
|
|
|
|
export var marginRightProperty = new styleProperty.Property("marginRight", "margin-right",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isMarginValid), converters.numberConverter);
|
|
|
|
export var marginTopProperty = new styleProperty.Property("marginTop", "margin-top",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isMarginValid), converters.numberConverter);
|
|
|
|
export var marginBottomProperty = new styleProperty.Property("marginBottom", "margin-bottom",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isMarginValid), converters.numberConverter);
|
|
|
|
export var paddingLeftProperty = new styleProperty.Property("paddingLeft", "padding-left",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isPaddingValid), converters.numberConverter);
|
|
|
|
export var paddingRightProperty = new styleProperty.Property("paddingRight", "padding-right",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isPaddingValid), converters.numberConverter);
|
|
|
|
export var paddingTopProperty = new styleProperty.Property("paddingTop", "padding-top",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isPaddingValid), converters.numberConverter);
|
|
|
|
export var paddingBottomProperty = new styleProperty.Property("paddingBottom", "padding-bottom",
|
|
new observable.PropertyMetadata(0, observable.PropertyMetadataSettings.AffectsLayout, null, isPaddingValid), converters.numberConverter);
|
|
|
|
function isVisibilityValid(value: string): boolean {
|
|
return value === enums.Visibility.visible || value === enums.Visibility.collapsed;
|
|
}
|
|
|
|
function setLayoutInfoVisibility(data: observable.PropertyChangeData) {
|
|
(<any>data.object)._view._isVisibleCache = data.newValue !== enums.Visibility.collapsed;
|
|
}
|
|
|
|
export var visibilityProperty = new styleProperty.Property("visibility", "visibility",
|
|
new observable.PropertyMetadata(enums.Visibility.visible, observable.PropertyMetadataSettings.AffectsLayout, setLayoutInfoVisibility, isVisibilityValid),
|
|
converters.visibilityConverter);
|
|
|
|
function isPaddingValid(value: number): boolean {
|
|
return isFinite(value) && !isNaN(value) && value >= 0;
|
|
}
|
|
|
|
function isMarginValid(value: number): boolean {
|
|
return isFinite(value) && !isNaN(value);
|
|
}
|
|
|
|
function isOpacityValid(value: string): boolean {
|
|
var parsedValue: number = parseFloat(value);
|
|
return !isNaN(parsedValue) && 0 <= parsedValue && parsedValue <= 1;
|
|
}
|
|
|
|
export var opacityProperty = new styleProperty.Property("opacity", "opacity",
|
|
new observable.PropertyMetadata(1.0, observable.PropertyMetadataSettings.None, undefined, isOpacityValid),
|
|
converters.opacityConverter);
|
|
|
|
// register default stylers once all properties are defined.
|
|
stylers._registerDefaultStylers(); |