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"); // key is the property id and value is Dictionary; 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 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 verticalAlignment(): string { return this._getValue(verticalAlignmentProperty); } set verticalAlignment(value: string) { this._setValue(verticalAlignmentProperty, value, observable.ValueSource.Local); } get horizontalAlignment(): string { return this._getValue(horizontalAlignmentProperty); } set horizontalAlignment(value: string) { this._setValue(horizontalAlignmentProperty, value, observable.ValueSource.Local); } get padding(): string { return this._getValue(paddingProperty); } set padding(value: string) { this._setValue(paddingProperty, value, observable.ValueSource.Local); } get margin(): string { return this._getValue(marginProperty); } set margin(value: string) { this._setValue(marginProperty, value, observable.ValueSource.Local); } public get marginLeft(): number { return this._getValue(marginLeftProperty); } public set marginLeft(value: number) { this._setValue(marginLeftProperty, value, observable.ValueSource.Local); } public get marginTop(): number { return this._getValue(marginTopProperty); } public set marginTop(value: number) { this._setValue(marginTopProperty, value, observable.ValueSource.Local); } public get marginRight(): number { return this._getValue(marginRightProperty); } public set marginRight(value: number) { this._setValue(marginRightProperty, value, observable.ValueSource.Local); } public get marginBottom(): number { return this._getValue(marginBottomProperty); } public set marginBottom(value: number) { this._setValue(marginBottomProperty, value, observable.ValueSource.Local); } public get paddingLeft(): number { return this._getValue(paddingLeftProperty); } public set paddingLeft(value: number) { this._setValue(paddingLeftProperty, value, observable.ValueSource.Local); } public get paddingTop(): number { return this._getValue(paddingTopProperty); } public set paddingTop(value: number) { this._setValue(paddingTopProperty, value, observable.ValueSource.Local); } public get paddingRight(): number { return this._getValue(paddingRightProperty); } public set paddingRight(value: number) { this._setValue(paddingRightProperty, value, observable.ValueSource.Local); } public get paddingBottom(): number { return this._getValue(paddingBottomProperty); } public set paddingBottom(value: number) { this._setValue(paddingBottomProperty, 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 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 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)) { (handler).resetProperty(property, this._view); } else { (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 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 =