From 0ad198165ccca630fe9e14534bf830494cb78d89 Mon Sep 17 00:00:00 2001 From: hshristov Date: Thu, 16 Jul 2015 01:22:16 +0300 Subject: [PATCH] Layouts are now implemented natively for Android. --- CrossPlatformModules.csproj | 71 ++- apps/tests/pages/page9.ts | 25 +- apps/ui-tests-app/layouts/myview.ts | 4 +- ui/content-view/content-view.ts | 2 + ui/core/dependency-observable.ts | 2 +- ui/core/view-common.ts | 40 +- ui/core/view.android.ts | 69 +-- ui/core/view.d.ts | 25 -- ui/core/view.ios.ts | 11 +- ui/frame/frame-common.ts | 19 - ui/frame/frame.android.ts | 3 +- ui/image/image-common.ts | 116 +---- ui/image/image.android.ts | 6 +- ui/image/image.ios.ts | 74 ++++ .../absolute-layout/absolute-layout-common.ts | 69 +++ .../absolute-layout.android.ts | 52 +++ .../absolute-layout/absolute-layout.d.ts | 4 +- ...olute-layout.ts => absolute-layout.ios.ts} | 57 +-- ui/layouts/dock-layout/dock-layout-common.ts | 58 +++ ui/layouts/dock-layout/dock-layout.android.ts | 69 +++ ui/layouts/dock-layout/dock-layout.d.ts | 4 +- .../{dock-layout.ts => dock-layout.ios.ts} | 51 +-- ui/layouts/grid-layout/grid-layout-common.ts | 370 ++++++++++++++++ ui/layouts/grid-layout/grid-layout.android.ts | 111 +++++ ui/layouts/grid-layout/grid-layout.d.ts | 4 +- .../{grid-layout.ts => grid-layout.ios.ts} | 352 ++------------- ui/layouts/layout-base.d.ts | 60 +++ ui/layouts/{layout.ts => layout-base.ts} | 48 +-- ui/layouts/layout.android.ts | 72 ++++ ui/layouts/layout.d.ts | 64 +-- ui/layouts/layout.ios.ts | 41 ++ .../stack-layout/stack-layout-common.ts | 25 ++ .../stack-layout/stack-layout.android.ts | 38 ++ ui/layouts/stack-layout/stack-layout.d.ts | 4 +- .../{stack-layout.ts => stack-layout.ios.ts} | 31 +- ui/layouts/wrap-layout/wrap-layout-common.ts | 49 +++ ui/layouts/wrap-layout/wrap-layout.android.ts | 46 ++ ui/layouts/wrap-layout/wrap-layout.d.ts | 4 +- .../{wrap-layout.ts => wrap-layout.ios.ts} | 69 +-- ui/scroll-view/scroll-view.android.ts | 161 +------ ui/styling/style.ts | 403 +++++++++++------- ui/styling/stylers.android.ts | 119 +++++- utils/number-utils.ts | 7 +- utils/utils-common.ts | 1 + utils/utils.d.ts | 4 + 45 files changed, 1727 insertions(+), 1187 deletions(-) create mode 100644 ui/layouts/absolute-layout/absolute-layout-common.ts create mode 100644 ui/layouts/absolute-layout/absolute-layout.android.ts rename ui/layouts/absolute-layout/{absolute-layout.ts => absolute-layout.ios.ts} (59%) create mode 100644 ui/layouts/dock-layout/dock-layout-common.ts create mode 100644 ui/layouts/dock-layout/dock-layout.android.ts rename ui/layouts/dock-layout/{dock-layout.ts => dock-layout.ios.ts} (77%) create mode 100644 ui/layouts/grid-layout/grid-layout-common.ts create mode 100644 ui/layouts/grid-layout/grid-layout.android.ts rename ui/layouts/grid-layout/{grid-layout.ts => grid-layout.ios.ts} (73%) create mode 100644 ui/layouts/layout-base.d.ts rename ui/layouts/{layout.ts => layout-base.ts} (69%) create mode 100644 ui/layouts/layout.android.ts create mode 100644 ui/layouts/layout.ios.ts create mode 100644 ui/layouts/stack-layout/stack-layout-common.ts create mode 100644 ui/layouts/stack-layout/stack-layout.android.ts rename ui/layouts/stack-layout/{stack-layout.ts => stack-layout.ios.ts} (88%) create mode 100644 ui/layouts/wrap-layout/wrap-layout-common.ts create mode 100644 ui/layouts/wrap-layout/wrap-layout.android.ts rename ui/layouts/wrap-layout/{wrap-layout.ts => wrap-layout.ios.ts} (78%) diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 8ac0fcb0a..17e30383e 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -527,6 +527,62 @@ + + + absolute-layout.d.ts + + + absolute-layout.d.ts + + + absolute-layout.d.ts + + + dock-layout.d.ts + + + dock-layout.d.ts + + + dock-layout.d.ts + + + grid-layout.d.ts + + + grid-layout.d.ts + + + grid-layout.d.ts + + + layout.d.ts + + + layout.d.ts + + + layout-base.d.ts + + + + stack-layout.d.ts + + + stack-layout.d.ts + + + stack-layout.d.ts + + + wrap-layout.d.ts + + + wrap-layout.d.ts + + + wrap-layout.d.ts + repeater.d.ts @@ -588,26 +644,11 @@ image-cache.d.ts - - absolute-layout.d.ts - - - dock-layout.d.ts - - - grid-layout.d.ts - - - layout.d.ts - - - stack-layout.d.ts - wrap-layout.d.ts diff --git a/apps/tests/pages/page9.ts b/apps/tests/pages/page9.ts index 19babcf96..80cdf5741 100644 --- a/apps/tests/pages/page9.ts +++ b/apps/tests/pages/page9.ts @@ -3,12 +3,9 @@ import slider = require("ui/slider"); import imageSource = require("image-source"); import gridModule = require("ui/layouts/grid-layout"); import enums = require("ui/enums"); - +import img = require("ui/image"); +var Image = img.Image; export function createPage() { - var StackLayout = require("ui/layouts/stack-layout").StackLayout; - var Image = require("ui/image").Image; - - var stack = new StackLayout(); var grid = new gridModule.GridLayout(); grid.addColumn(new gridModule.ItemSpec(1, gridModule.GridUnitType.auto)); @@ -17,24 +14,16 @@ export function createPage() { grid.addRow(new gridModule.ItemSpec(1, gridModule.GridUnitType.auto)); grid.addRow(new gridModule.ItemSpec(1, gridModule.GridUnitType.star)); - var sldr = new slider.Slider(); - gridModule.GridLayout.setColumnSpan(sldr, 2); - sldr.maxValue = 500; - - stack.addChild(sldr); - stack.addChild(grid); - var image = new Image(); image.stretch = enums.Stretch.fill; - image.verticalAlignment = 2; - image.horizontalAlignment = 1; + image.verticalAlignment = enums.VerticalAlignment.bottom; + image.horizontalAlignment = enums.HorizontalAlignment.center; - image.source = imageSource.fromFile(__dirname + "test.png"); + image.imageSource = imageSource.fromFile(__dirname + "/test.png"); grid.addChild(image); var page = new pages.Page(); - page.content = stack; + page.content = grid; page.css = "GridLayout { background-color: pink } image { background-color: green }"; return page; -} -//export var Page = page; \ No newline at end of file +} \ No newline at end of file diff --git a/apps/ui-tests-app/layouts/myview.ts b/apps/ui-tests-app/layouts/myview.ts index 9e434a7d9..9c159f087 100644 --- a/apps/ui-tests-app/layouts/myview.ts +++ b/apps/ui-tests-app/layouts/myview.ts @@ -1,7 +1,7 @@ import observable = require("data/observable"); import enums = require("ui/enums"); import view = require("ui/core/view"); -import layouts = require("ui/layouts/layout"); +import layouts = require("ui/layouts/layout-base"); export class ViewModel extends observable.Observable { @@ -67,7 +67,7 @@ export class ViewModel extends observable.Observable { public onVisibile(args: { eventName: string, object: any }): void { var view: view.View = args.object; - var layout = view.parent; + var layout = view.parent; var child = layout.getViewById("collapse"); child.visibility = enums.Visibility.visible; diff --git a/ui/content-view/content-view.ts b/ui/content-view/content-view.ts index d8ec56e29..53ceae932 100644 --- a/ui/content-view/content-view.ts +++ b/ui/content-view/content-view.ts @@ -47,6 +47,7 @@ export class ContentView extends view.CustomLayoutView implements definition.Con } } + // This method won't be called in Android because we use the native android layout. public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { var result = view.View.measureChild(this, this.content, widthMeasureSpec, heightMeasureSpec); @@ -66,6 +67,7 @@ export class ContentView extends view.CustomLayoutView implements definition.Con this.setMeasuredDimension(widthAndState, heightAndState); } + // This method won't be called in Android because we use the native android layout. public onLayout(left: number, top: number, right: number, bottom: number): void { view.View.layoutChild(this, this.content, 0, 0, right - left, bottom - top); } diff --git a/ui/core/dependency-observable.ts b/ui/core/dependency-observable.ts index 22e50c72c..ef8d13b12 100644 --- a/ui/core/dependency-observable.ts +++ b/ui/core/dependency-observable.ts @@ -68,7 +68,7 @@ export class PropertyMetadata implements definition.PropertyMetadata { equalityComparer?: definition.PropertyEqualityComparer) { this._defaultValue = defaultValue; this._options = options; - if (types.isUndefined(this._options)) { + if (types.isNullOrUndefined(this._options)) { this._options = PropertyMetadataSettings.None; } this._onChanged = onChanged; diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index e79f02477..bc83e1d43 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -128,6 +128,7 @@ export class View extends proxy.ProxyObject implements definition.View { private _requestedVisualState: string; private _isLoaded: boolean; private _isLayoutValid: boolean = false; + public _domId: number; public _isAddedToNativeVisualTree = false; @@ -293,41 +294,6 @@ export class View extends proxy.ProxyObject implements definition.View { this.style.marginBottom = value; } - get padding(): string { - return this.style.padding; - } - set padding(value: string) { - this.style.padding = value; - } - - get paddingLeft(): number { - return this.style.paddingLeft; - } - set paddingLeft(value: number) { - this.style.paddingLeft = value; - } - - get paddingTop(): number { - return this.style.paddingTop; - } - set paddingTop(value: number) { - this.style.paddingTop = value; - } - - get paddingRight(): number { - return this.style.paddingRight; - } - set paddingRight(value: number) { - this.style.paddingRight = value; - } - - get paddingBottom(): number { - return this.style.paddingBottom; - } - set paddingBottom(value: number) { - this.style.paddingBottom = value; - } - get horizontalAlignment(): string { return this.style.horizontalAlignment; } @@ -510,11 +476,11 @@ export class View extends proxy.ProxyObject implements definition.View { } public getMeasuredWidth(): number { - return this._measuredWidth; + return this._measuredWidth & utils.layout.MEASURED_SIZE_MASK; } public getMeasuredHeight(): number { - return this._measuredHeight; + return this._measuredHeight & utils.layout.MEASURED_SIZE_MASK; } public setMeasuredDimension(measuredWidth: number, measuredHeight: number): void { diff --git a/ui/core/view.android.ts b/ui/core/view.android.ts index f745ad301..94e30ade3 100644 --- a/ui/core/view.android.ts +++ b/ui/core/view.android.ts @@ -32,16 +32,13 @@ function onIsUserInteractionEnabledPropertyChanged(data: dependencyObservable.Pr (viewCommon.View.isUserInteractionEnabledProperty.metadata).onSetNativeValue = onIsUserInteractionEnabledPropertyChanged; export var NativeViewGroup = (android.view.ViewGroup).extend({ - get owner() { - return this[OWNER]; - }, onMeasure: function (widthMeasureSpec, heightMeasureSpec) { - var owner: viewDefinition.View = this.owner; + var owner: viewDefinition.View = this[OWNER]; owner.onMeasure(widthMeasureSpec, heightMeasureSpec); this.setMeasuredDimension(owner.getMeasuredWidth(), owner.getMeasuredHeight()); }, onLayout: function (changed: boolean, left: number, top: number, right: number, bottom: number): void { - var owner: viewDefinition.View = this.owner; + var owner: viewDefinition.View = this[OWNER]; owner.onLayout(left, top, right, bottom); } }); @@ -239,6 +236,14 @@ export class View extends viewCommon.View { return this.android; } + get isLayoutValid(): boolean { + if (this._nativeView) { + return !this._nativeView.isLayoutRequested(); + } + + return false; + } + public layoutNativeView(left: number, top: number, right: number, bottom: number): void { if (this._nativeView) { this._nativeView.layout(left, top, right, bottom); @@ -287,8 +292,8 @@ export class View extends viewCommon.View { } public focus(): boolean { - if (this.android) { - return this.android.requestFocus(); + if (this._nativeView) { + return this._nativeView.requestFocus(); } return false; @@ -328,15 +333,9 @@ export class CustomLayoutView extends View implements viewDefinition.CustomLayou } public _createUI() { - this._viewGroup = new NativeViewGroup(this._context); - this._viewGroup[OWNER] = this; + this._viewGroup = new org.nativescript.widgets.ContentLayout(this._context); } - //public _onDetached(force?: boolean) { - // delete this._viewGroup[OWNER]; - // super._onDetached(force); - //} - public _addViewToNativeVisualTree(child: View): boolean { super._addViewToNativeVisualTree(child); @@ -356,46 +355,4 @@ export class CustomLayoutView extends View implements viewDefinition.CustomLayou trace.notifyEvent(child, "childInLayoutRemovedFromNativeVisualTree"); } } - - public measure(widthMeasureSpec: number, heightMeasureSpec: number): void { - this._setCurrentMeasureSpecs(widthMeasureSpec, heightMeasureSpec); - - var view = this._nativeView; - if (view) { - var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); - var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); - - var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); - var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); - - trace.write(this + " :measure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); - view.measure(widthMeasureSpec, heightMeasureSpec); - } - } - - public layout(left: number, top: number, right: number, bottom: number): void { - this._setCurrentLayoutBounds(left, top, right, bottom); - - var view = this._nativeView; - if (view) { - this.layoutNativeView(left, top, right, bottom); - trace.write(this + " :layout: " + left + ", " + top + ", " + (right - left) + ", " + (bottom - top), trace.categories.Layout); - } - } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - // Don't call super because it will trigger measure again. - - var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); - var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); - - var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); - var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); - trace.write(this + " :onMeasure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); - } - - public onLayout(left: number, top: number, right: number, bottom: number): void { - // Don't call super because it will trigger layout again. - trace.write(this + " :onLayout: " + left + ", " + top + ", " + (right - left) + ", " + (bottom - top), trace.categories.Layout); - } } \ No newline at end of file diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index 94ca5efce..c4772c36c 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -205,31 +205,6 @@ declare module "ui/core/view" { */ marginBottom: number; - /** - * Gets or sets padding style property. - */ - padding: string; - - /** - * Specify the left padding of this view. - */ - paddingLeft: number; - - /** - * Specify the top padding of this view. - */ - paddingTop: number; - - /** - * Specify the right padding of this view. - */ - paddingRight: number; - - /** - * Specify the bottom padding of this view. - */ - paddingBottom: number; - /** * Gets or sets the alignment of this view within its parent along the Horizontal axis. */ diff --git a/ui/core/view.ios.ts b/ui/core/view.ios.ts index 5f22fa455..b926fc91f 100644 --- a/ui/core/view.ios.ts +++ b/ui/core/view.ios.ts @@ -85,13 +85,18 @@ export class View extends viewCommon.View { // For UILabel and UIImage. view.userInteractionEnabled = true; } + + get isLayoutRequested(): boolean { + return (this._privateFlags & PFLAG_FORCE_LAYOUT) === PFLAG_FORCE_LAYOUT; + } public requestLayout(): void { super.requestLayout(); this._privateFlags |= PFLAG_FORCE_LAYOUT; - if (this.parent) { - this.parent.requestLayout(); + var parent = this.parent; + if (parent && !parent.isLayoutRequested) { + parent.requestLayout(); } } @@ -236,7 +241,7 @@ export class CustomLayoutView extends View { } public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - // Don't call super because it will trigger measure again. + // Don't call super because it will set MeasureDimension. This method must be overriden and calculate its measuredDimensions. var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts index eef4bfff3..9d9c494c7 100644 --- a/ui/frame/frame-common.ts +++ b/ui/frame/frame-common.ts @@ -327,25 +327,6 @@ export class Frame extends view.CustomLayoutView implements definition.Frame { return 0; } - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); - var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); - - var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); - var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); - - var result = view.View.measureChild(this, this.currentPage, widthMeasureSpec, utils.layout.makeMeasureSpec(height - this.navigationBarHeight, heightMode)); - - var widthAndState = view.View.resolveSizeAndState(result.measuredWidth, width, widthMode, 0); - var heightAndState = view.View.resolveSizeAndState(result.measuredHeight, height, heightMode, 0); - - this.setMeasuredDimension(widthAndState, heightAndState); - } - - public onLayout(left: number, top: number, right: number, bottom: number): void { - view.View.layoutChild(this, this.currentPage, 0, this.navigationBarHeight, right - left, bottom - top); - } - // We don't need to put Page as visual child. Don't call super. public _addViewToNativeVisualTree(child: view.View): boolean { return true; diff --git a/ui/frame/frame.android.ts b/ui/frame/frame.android.ts index 782de2f24..e0e8f7c01 100644 --- a/ui/frame/frame.android.ts +++ b/ui/frame/frame.android.ts @@ -373,8 +373,7 @@ var NativeActivity = { this.androidFrame.setActivity(this); // Create and set content container. - var root = new view.NativeViewGroup(this); - root[OWNER] = this.frame; + var root = new org.nativescript.widgets.ContentLayout(this); this.androidFrame.rootViewGroup = root; this.androidFrame.rootViewGroup.setId(this.frame.containerViewId); diff --git a/ui/image/image-common.ts b/ui/image/image-common.ts index 233d36265..096a44617 100644 --- a/ui/image/image-common.ts +++ b/ui/image/image-common.ts @@ -3,9 +3,7 @@ import view = require("ui/core/view"); import proxy = require("ui/core/proxy"); import imageSource = require("image-source"); import definition = require("ui/image"); -import trace = require("trace"); import enums = require("ui/enums"); -import utils = require("utils/utils"); import types = require("utils/types"); var SRC = "src"; @@ -49,43 +47,18 @@ function onSrcPropertyChanged(data: dependencyObservable.PropertyChangeData) { export class Image extends view.View implements definition.Image { - public static srcProperty = new dependencyObservable.Property( - SRC, - IMAGE, - new proxy.PropertyMetadata( - undefined, - dependencyObservable.PropertyMetadataSettings.None, - onSrcPropertyChanged - ) - ); + public static srcProperty = new dependencyObservable.Property(SRC, IMAGE, + new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, onSrcPropertyChanged)); - public static imageSourceProperty = new dependencyObservable.Property( - IMAGE_SOURCE, - IMAGE, - new proxy.PropertyMetadata( - undefined, - // None on purpose. for iOS we trigger it manually if needed. Android layout handles it. - dependencyObservable.PropertyMetadataSettings.None - ) - ); + // None on purpose. for iOS we trigger it manually if needed. Android layout handles it. + public static imageSourceProperty = new dependencyObservable.Property(IMAGE_SOURCE, IMAGE, + new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None)); - public static isLoadingProperty = new dependencyObservable.Property( - ISLOADING, - IMAGE, - new proxy.PropertyMetadata( - false, - dependencyObservable.PropertyMetadataSettings.None - ) - ); + public static isLoadingProperty = new dependencyObservable.Property(ISLOADING, IMAGE, + new proxy.PropertyMetadata(false, dependencyObservable.PropertyMetadataSettings.None)); - public static stretchProperty = new dependencyObservable.Property( - STRETCH, - IMAGE, - new proxy.PropertyMetadata( - enums.Stretch.aspectFit, - dependencyObservable.PropertyMetadataSettings.AffectsLayout - ) - ); + public static stretchProperty = new dependencyObservable.Property(STRETCH, IMAGE, + new proxy.PropertyMetadata(enums.Stretch.aspectFit, dependencyObservable.PropertyMetadataSettings.AffectsLayout)); constructor(options?: definition.Options) { super(options); @@ -119,75 +92,4 @@ export class Image extends view.View implements definition.Image { public _setNativeImage(nativeImage: any) { // } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - - // We don't call super because we measure native view with specific size. - var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); - var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); - - var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); - var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); - trace.write(this + " :onMeasure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); - - var nativeWidth = this.imageSource ? this.imageSource.width : 0; - var nativeHeight = this.imageSource ? this.imageSource.height : 0; - - var measureWidth = Math.max(nativeWidth, this.minWidth); - var measureHeight = Math.max(nativeHeight, this.minHeight); - - var finiteWidth: boolean = widthMode !== utils.layout.UNSPECIFIED; - var finiteHeight: boolean = heightMode !== utils.layout.UNSPECIFIED; - - if (nativeWidth !== 0 && nativeHeight !== 0 && (finiteWidth || finiteHeight)) { - var scale = Image.computeScaleFactor(width, height, finiteWidth, finiteHeight, nativeWidth, nativeHeight, this.stretch); - var resultW = Math.floor(nativeWidth * scale.width); - var resultH = Math.floor(nativeHeight * scale.height); - - measureWidth = finiteWidth ? Math.min(resultW, width) : resultW; - measureHeight = finiteHeight ? Math.min(resultH, height) : resultH; - - trace.write("Image stretch: " + this.stretch + - ", nativeWidth: " + nativeWidth + - ", nativeHeight: " + nativeHeight, trace.categories.Layout); - } - - var widthAndState = view.View.resolveSizeAndState(measureWidth, width, widthMode, 0); - var heightAndState = view.View.resolveSizeAndState(measureHeight, height, heightMode, 0); - - this.setMeasuredDimension(widthAndState, heightAndState); - } - - private static computeScaleFactor(measureWidth: number, measureHeight: number, widthIsFinite: boolean, heightIsFinite: boolean, nativeWidth: number, nativeHeight: number, imageStretch: string): { width: number; height: number } { - var scaleW = 1; - var scaleH = 1; - - if ((imageStretch === enums.Stretch.aspectFill || imageStretch === enums.Stretch.aspectFit || imageStretch === enums.Stretch.fill) && - (widthIsFinite || heightIsFinite)) { - - scaleW = (nativeWidth > 0) ? measureWidth / nativeWidth : 0; - scaleH = (nativeHeight > 0) ? measureHeight / nativeHeight : 0; - - if (!widthIsFinite) { - scaleW = scaleH; - } - else if (!heightIsFinite) { - scaleH = scaleW; - } - else { - // No infinite dimensions. - switch (imageStretch) { - case enums.Stretch.aspectFit: - scaleH = scaleW < scaleH ? scaleW : scaleH; - scaleW = scaleH; - break; - case enums.Stretch.aspectFill: - scaleH = scaleW > scaleH ? scaleW : scaleH; - scaleW = scaleH; - break; - } - } - } - return { width: scaleW, height: scaleH }; - } } diff --git a/ui/image/image.android.ts b/ui/image/image.android.ts index 6bb66de94..a3faa8a8b 100644 --- a/ui/image/image.android.ts +++ b/ui/image/image.android.ts @@ -42,14 +42,14 @@ function onImageSourcePropertyChanged(data: dependencyObservable.PropertyChangeD (imageCommon.Image.stretchProperty.metadata).onSetNativeValue = onStretchPropertyChanged; export class Image extends imageCommon.Image { - private _android: android.widget.ImageView; + private _android: org.nativescript.widgets.ImageView; - get android(): android.widget.ImageView { + get android(): org.nativescript.widgets.ImageView { return this._android; } public _createUI() { - this._android = new android.widget.ImageView(this._context); + this._android = new org.nativescript.widgets.ImageView(this._context); } public _setNativeImage(nativeImage: any) { diff --git a/ui/image/image.ios.ts b/ui/image/image.ios.ts index c1a5f5cde..54bb91752 100644 --- a/ui/image/image.ios.ts +++ b/ui/image/image.ios.ts @@ -3,6 +3,9 @@ import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); import definition = require("ui/image"); import enums = require("ui/enums"); +import utils = require("utils/utils"); +import trace = require("trace"); +import view = require("ui/core/view"); global.moduleMerge(imageCommon, exports); @@ -59,4 +62,75 @@ export class Image extends imageCommon.Image { this.requestLayout(); } } + + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + + // We don't call super because we measure native view with specific size. + var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); + var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); + + var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); + var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); + trace.write(this + " :onMeasure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); + + var nativeWidth = this.imageSource ? this.imageSource.width : 0; + var nativeHeight = this.imageSource ? this.imageSource.height : 0; + + var measureWidth = Math.max(nativeWidth, this.minWidth); + var measureHeight = Math.max(nativeHeight, this.minHeight); + + var finiteWidth: boolean = widthMode !== utils.layout.UNSPECIFIED; + var finiteHeight: boolean = heightMode !== utils.layout.UNSPECIFIED; + + if (nativeWidth !== 0 && nativeHeight !== 0 && (finiteWidth || finiteHeight)) { + var scale = Image.computeScaleFactor(width, height, finiteWidth, finiteHeight, nativeWidth, nativeHeight, this.stretch); + var resultW = Math.floor(nativeWidth * scale.width); + var resultH = Math.floor(nativeHeight * scale.height); + + measureWidth = finiteWidth ? Math.min(resultW, width) : resultW; + measureHeight = finiteHeight ? Math.min(resultH, height) : resultH; + + trace.write("Image stretch: " + this.stretch + + ", nativeWidth: " + nativeWidth + + ", nativeHeight: " + nativeHeight, trace.categories.Layout); + } + + var widthAndState = view.View.resolveSizeAndState(measureWidth, width, widthMode, 0); + var heightAndState = view.View.resolveSizeAndState(measureHeight, height, heightMode, 0); + + this.setMeasuredDimension(widthAndState, heightAndState); + } + + private static computeScaleFactor(measureWidth: number, measureHeight: number, widthIsFinite: boolean, heightIsFinite: boolean, nativeWidth: number, nativeHeight: number, imageStretch: string): { width: number; height: number } { + var scaleW = 1; + var scaleH = 1; + + if ((imageStretch === enums.Stretch.aspectFill || imageStretch === enums.Stretch.aspectFit || imageStretch === enums.Stretch.fill) && + (widthIsFinite || heightIsFinite)) { + + scaleW = (nativeWidth > 0) ? measureWidth / nativeWidth : 0; + scaleH = (nativeHeight > 0) ? measureHeight / nativeHeight : 0; + + if (!widthIsFinite) { + scaleW = scaleH; + } + else if (!heightIsFinite) { + scaleH = scaleW; + } + else { + // No infinite dimensions. + switch (imageStretch) { + case enums.Stretch.aspectFit: + scaleH = scaleW < scaleH ? scaleW : scaleH; + scaleW = scaleH; + break; + case enums.Stretch.aspectFill: + scaleH = scaleW > scaleH ? scaleW : scaleH; + scaleW = scaleH; + break; + } + } + } + return { width: scaleW, height: scaleH }; + } } \ No newline at end of file diff --git a/ui/layouts/absolute-layout/absolute-layout-common.ts b/ui/layouts/absolute-layout/absolute-layout-common.ts new file mode 100644 index 000000000..cbbb89b18 --- /dev/null +++ b/ui/layouts/absolute-layout/absolute-layout-common.ts @@ -0,0 +1,69 @@ +import layouts = require("ui/layouts/layout-base"); +import definition = require("ui/layouts/absolute-layout"); +import dependencyObservable = require("ui/core/dependency-observable"); +import view = require("ui/core/view"); +import proxy = require("ui/core/proxy"); + +function validateArgs(element: view.View): view.View { + if (!element) { + throw new Error("element cannot be null or undefinied."); + } + return element; +} + +export class AbsoluteLayout extends layouts.LayoutBase implements definition.AbsoluteLayout { + + private static isValid(value: number): boolean { + return isFinite(value); + } + + private static onLeftPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var uiView = data.object; + if (uiView instanceof view.View) { + var layout = uiView.parent; + if (layout instanceof AbsoluteLayout) { + layout.onLeftChanged(uiView, data.oldValue, data.newValue); + } + } + } + + private static onTopPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var uiView = data.object; + if (uiView instanceof view.View) { + var layout = uiView.parent; + if (layout instanceof AbsoluteLayout) { + layout.onTopChanged(uiView, data.oldValue, data.newValue); + } + } + } + + public static leftProperty = new dependencyObservable.Property("left", "AbsoluteLayout", + new proxy.PropertyMetadata(0, undefined, AbsoluteLayout.onLeftPropertyChanged, AbsoluteLayout.isValid)); + + public static topProperty = new dependencyObservable.Property("top", "AbsoluteLayout", + new proxy.PropertyMetadata(0, undefined, AbsoluteLayout.onTopPropertyChanged, AbsoluteLayout.isValid)); + + public static getLeft(element: view.View): number { + return validateArgs(element)._getValue(AbsoluteLayout.leftProperty); + } + + public static setLeft(element: view.View, value: number): void { + validateArgs(element)._setValue(AbsoluteLayout.leftProperty, value); + } + + public static getTop(element: view.View): number { + return validateArgs(element)._getValue(AbsoluteLayout.topProperty); + } + + public static setTop(element: view.View, value: number): void { + validateArgs(element)._setValue(AbsoluteLayout.topProperty, value); + } + + protected onLeftChanged(view: view.View, oldValue: number, newValue: number) { + // + } + + protected onTopChanged(view: view.View, oldValue: number, newValue: number) { + // + } +} \ No newline at end of file diff --git a/ui/layouts/absolute-layout/absolute-layout.android.ts b/ui/layouts/absolute-layout/absolute-layout.android.ts new file mode 100644 index 000000000..4dff79878 --- /dev/null +++ b/ui/layouts/absolute-layout/absolute-layout.android.ts @@ -0,0 +1,52 @@ +import utils = require("utils/utils"); +import view = require("ui/core/view"); +import common = require("ui/layouts/absolute-layout/absolute-layout-common"); +import dependencyObservable = require("ui/core/dependency-observable"); +import proxy = require("ui/core/proxy"); + +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); + +function setNativeProperty(data: dependencyObservable.PropertyChangeData, setter: (lp: org.nativescript.widgets.CommonLayoutParams) => void) { + + var uiView = data.object; + if (uiView instanceof view.View) { + var nativeView: android.view.View = uiView._nativeView; + + var lp = nativeView.getLayoutParams(); + if (!(lp instanceof org.nativescript.widgets.CommonLayoutParams)) { + lp = new org.nativescript.widgets.CommonLayoutParams(); + } + setter(lp); + nativeView.setLayoutParams(lp); + } +} + +function setNativeLeftProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.left = data.newValue * utils.layout.getDisplayDensity(); }); +} + +function setNativeTopProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.top = data.newValue * utils.layout.getDisplayDensity(); }); +} + +(common.AbsoluteLayout.leftProperty.metadata).onSetNativeValue = setNativeLeftProperty; +(common.AbsoluteLayout.topProperty.metadata).onSetNativeValue = setNativeTopProperty; + +export class AbsoluteLayout extends common.AbsoluteLayout { + + private _layout: org.nativescript.widgets.AbsoluteLayout; + + get android(): org.nativescript.widgets.AbsoluteLayout { + return this._layout; + } + + get _nativeView(): org.nativescript.widgets.AbsoluteLayout { + return this._layout; + } + + public _createUI() { + this._layout = new org.nativescript.widgets.AbsoluteLayout(this._context); + } +} \ No newline at end of file diff --git a/ui/layouts/absolute-layout/absolute-layout.d.ts b/ui/layouts/absolute-layout/absolute-layout.d.ts index 541950f18..401c9a2f1 100644 --- a/ui/layouts/absolute-layout/absolute-layout.d.ts +++ b/ui/layouts/absolute-layout/absolute-layout.d.ts @@ -1,12 +1,12 @@ declare module "ui/layouts/absolute-layout" { - import layout = require("ui/layouts/layout"); + import layout = require("ui/layouts/layout-base"); import view = require("ui/core/view"); import dependencyObservable = require("ui/core/dependency-observable"); /** * A layout that lets you specify exact locations (left/top coordinates) of its children. */ - class AbsoluteLayout extends layout.Layout { + class AbsoluteLayout extends layout.LayoutBase { /** * Represents the observable property backing the left property. diff --git a/ui/layouts/absolute-layout/absolute-layout.ts b/ui/layouts/absolute-layout/absolute-layout.ios.ts similarity index 59% rename from ui/layouts/absolute-layout/absolute-layout.ts rename to ui/layouts/absolute-layout/absolute-layout.ios.ts index cd311b673..bb150ad0a 100644 --- a/ui/layouts/absolute-layout/absolute-layout.ts +++ b/ui/layouts/absolute-layout/absolute-layout.ios.ts @@ -1,56 +1,19 @@ -import layouts = require("ui/layouts/layout"); -import definition = require("ui/layouts/absolute-layout"); -import utils = require("utils/utils"); -import dependencyObservable = require("ui/core/dependency-observable"); +import utils = require("utils/utils"); import view = require("ui/core/view"); -import numberUtils = require("utils/number-utils"); +import common = require("ui/layouts/absolute-layout/absolute-layout-common"); -function onPropertyChanged(data: dependencyObservable.PropertyChangeData) { - var uiView = data.object; - if (uiView instanceof view.View) { - var layout = uiView.parent; - if (layout instanceof AbsoluteLayout) { - layout.requestLayout(); - } - } -} +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); -export class AbsoluteLayout extends layouts.Layout implements definition.AbsoluteLayout { +export class AbsoluteLayout extends common.AbsoluteLayout { - public static leftProperty = new dependencyObservable.Property("left", "AbsoluteLayout", - new dependencyObservable.PropertyMetadata(0, undefined, onPropertyChanged, numberUtils.isFiniteNumber)); - - public static topProperty = new dependencyObservable.Property("top", "AbsoluteLayout", - new dependencyObservable.PropertyMetadata(0, undefined, onPropertyChanged, numberUtils.isFiniteNumber)); - - public static getLeft(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - - return element._getValue(AbsoluteLayout.leftProperty); + protected onLeftChanged(view: view.View, oldValue: number, newValue: number) { + this.requestLayout(); } - public static setLeft(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(AbsoluteLayout.leftProperty, value); - } - - public static getTop(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - - return element._getValue(AbsoluteLayout.topProperty); - } - - public static setTop(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(AbsoluteLayout.topProperty, value); + protected onTopChanged(view: view.View, oldValue: number, newValue: number) { + this.requestLayout(); } public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { diff --git a/ui/layouts/dock-layout/dock-layout-common.ts b/ui/layouts/dock-layout/dock-layout-common.ts new file mode 100644 index 000000000..9cc6f628f --- /dev/null +++ b/ui/layouts/dock-layout/dock-layout-common.ts @@ -0,0 +1,58 @@ +import layouts = require("ui/layouts/layout-base"); +import definition = require("ui/layouts/dock-layout"); +import dependencyObservable = require("ui/core/dependency-observable"); +import view = require("ui/core/view"); +import enums = require("ui/enums"); +import proxy = require("ui/core/proxy"); + +// on Android we explicitly set propertySettings to None because android will invalidate its layout (skip unnecessary native call). +var AffectsLayout = global.android ? dependencyObservable.PropertyMetadataSettings.None : dependencyObservable.PropertyMetadataSettings.AffectsLayout; + +function isDockValid(value: any): boolean { + return value === enums.Dock.left || value === enums.Dock.top || value === enums.Dock.right || value === enums.Dock.bottom; +} + +function validateArgs(element: view.View): view.View { + if (!element) { + throw new Error("element cannot be null or undefinied."); + } + return element; +} + +export class DockLayout extends layouts.LayoutBase implements definition.DockLayout { + + private static onDockPropertyChanged(data: dependencyObservable.PropertyChangeData) { + var uiView = data.object; + if (uiView instanceof view.View) { + var layout = uiView.parent; + if (layout instanceof DockLayout) { + layout.onDockChanged(uiView, data.oldValue, data.newValue); + } + } + } + + public static dockProperty = new dependencyObservable.Property( + "dock", "DockLayout", new proxy.PropertyMetadata(enums.Dock.left, undefined, DockLayout.onDockPropertyChanged, isDockValid)); + + public static stretchLastChildProperty = new dependencyObservable.Property( + "stretchLastChild", "DockLayout", new proxy.PropertyMetadata(true, AffectsLayout)); + + public static getDock(element: view.View): string { + return validateArgs(element)._getValue(DockLayout.dockProperty); + } + + public static setDock(element: view.View, value: string): void { + validateArgs(element)._setValue(DockLayout.dockProperty, value); + } + + get stretchLastChild(): boolean { + return this._getValue(DockLayout.stretchLastChildProperty); + } + set stretchLastChild(value: boolean) { + this._setValue(DockLayout.stretchLastChildProperty, value); + } + + protected onDockChanged(view: view.View, oldValue: number, newValue: number) { + // + } +} \ No newline at end of file diff --git a/ui/layouts/dock-layout/dock-layout.android.ts b/ui/layouts/dock-layout/dock-layout.android.ts new file mode 100644 index 000000000..42dee5c4e --- /dev/null +++ b/ui/layouts/dock-layout/dock-layout.android.ts @@ -0,0 +1,69 @@ +import definition = require("ui/layouts/dock-layout"); +import dependencyObservable = require("ui/core/dependency-observable"); +import view = require("ui/core/view"); +import enums = require("ui/enums"); +import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/dock-layout/dock-layout-common"); + +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); + +function setNativeDockProperty(data: dependencyObservable.PropertyChangeData) { + + var uiView = data.object; + if (uiView instanceof view.View) { + var nativeView: android.view.View = uiView._nativeView; + + var lp = nativeView.getLayoutParams(); + if (!(lp instanceof org.nativescript.widgets.CommonLayoutParams)) { + lp = new org.nativescript.widgets.CommonLayoutParams(); + } + + switch (data.newValue) { + case enums.Dock.left: + lp.dock = org.nativescript.widgets.Dock.left; + break; + case enums.Dock.top: + lp.dock = org.nativescript.widgets.Dock.top; + break; + case enums.Dock.right: + lp.dock = org.nativescript.widgets.Dock.right; + break; + case enums.Dock.bottom: + lp.dock = org.nativescript.widgets.Dock.bottom; + break; + default: + throw new Error("Invalid dock value: " + data.newValue + " on element: " + uiView); + } + + nativeView.setLayoutParams(lp); + } +} + +(common.DockLayout.dockProperty.metadata).onSetNativeValue = setNativeDockProperty; + +export class DockLayout extends common.DockLayout { + + static setNativeStretchLastChildProperty(data: dependencyObservable.PropertyChangeData) { + var dockLayout = data.object; + var nativeView = dockLayout._nativeView; + nativeView.setStretchLastChild(data.newValue); + } + + private _layout: org.nativescript.widgets.DockLayout; + + get android(): org.nativescript.widgets.DockLayout { + return this._layout; + } + + get _nativeView(): org.nativescript.widgets.DockLayout { + return this._layout; + } + + public _createUI() { + this._layout = new org.nativescript.widgets.DockLayout(this._context); + } +} + +(common.DockLayout.stretchLastChildProperty.metadata).onSetNativeValue = DockLayout.setNativeStretchLastChildProperty; \ No newline at end of file diff --git a/ui/layouts/dock-layout/dock-layout.d.ts b/ui/layouts/dock-layout/dock-layout.d.ts index 028cefcc1..46a29c6a3 100644 --- a/ui/layouts/dock-layout/dock-layout.d.ts +++ b/ui/layouts/dock-layout/dock-layout.d.ts @@ -1,12 +1,12 @@ declare module "ui/layouts/dock-layout" { import view = require("ui/core/view"); - import layout = require("ui/layouts/layout"); + import layout = require("ui/layouts/layout-base"); import dependencyObservable = require("ui/core/dependency-observable"); /** * A Layout that arranges its children at its outer edges, and allows its last child to take up the remaining space. */ - class DockLayout extends layout.Layout { + class DockLayout extends layout.LayoutBase { /** * Represents the observable property backing the dock property. diff --git a/ui/layouts/dock-layout/dock-layout.ts b/ui/layouts/dock-layout/dock-layout.ios.ts similarity index 77% rename from ui/layouts/dock-layout/dock-layout.ts rename to ui/layouts/dock-layout/dock-layout.ios.ts index a30158733..1965f470f 100644 --- a/ui/layouts/dock-layout/dock-layout.ts +++ b/ui/layouts/dock-layout/dock-layout.ios.ts @@ -1,53 +1,12 @@ -import layouts = require("ui/layouts/layout"); -import definition = require("ui/layouts/dock-layout"); -import utils = require("utils/utils"); -import dependencyObservable = require("ui/core/dependency-observable"); +import utils = require("utils/utils"); import view = require("ui/core/view"); import enums = require("ui/enums"); -import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/dock-layout/dock-layout-common"); -function isDockValid(value: any): boolean { - return value === enums.Dock.left || value === enums.Dock.top || value === enums.Dock.right || value === enums.Dock.bottom; -} +export class DockLayout extends common.DockLayout { -function onDockPropertyChanged(data: dependencyObservable.PropertyChangeData) { - var uiView = data.object; - if (uiView instanceof view.View) { - var layout = (uiView).parent; - if (layout instanceof DockLayout) { - layout.requestLayout(); - } - } -} - -export class DockLayout extends layouts.Layout implements definition.DockLayout { - - public static dockProperty = new dependencyObservable.Property( - "dock", "DockLayout", new dependencyObservable.PropertyMetadata(enums.Dock.left, undefined, onDockPropertyChanged, isDockValid)); - - public static stretchLastChildProperty = new dependencyObservable.Property( - "stretchLastChild", "DockLayout", new proxy.PropertyMetadata(true, dependencyObservable.PropertyMetadataSettings.AffectsLayout)); - - public static getDock(element: view.View): string { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - - return element._getValue(DockLayout.dockProperty); - } - - public static setDock(element: view.View, value: string): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(DockLayout.dockProperty, value); - } - - get stretchLastChild(): boolean { - return this._getValue(DockLayout.stretchLastChildProperty); - } - set stretchLastChild(value: boolean) { - this._setValue(DockLayout.stretchLastChildProperty, value); + protected onDockChanged(view: view.View, oldValue: number, newValue: number) { + this.requestLayout(); } public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { diff --git a/ui/layouts/grid-layout/grid-layout-common.ts b/ui/layouts/grid-layout/grid-layout-common.ts new file mode 100644 index 000000000..7c2c1a6b3 --- /dev/null +++ b/ui/layouts/grid-layout/grid-layout-common.ts @@ -0,0 +1,370 @@ +import layouts = require("ui/layouts/layout-base"); +import definition = require("ui/layouts/grid-layout"); +import utils = require("utils/utils"); +import dependencyObservable = require("ui/core/dependency-observable"); +import enums = require("ui/enums"); +import view = require("ui/core/view"); +import bindable = require("ui/core/bindable"); +import types = require("utils/types"); +import numberUtils = require("utils/number-utils"); +import proxy = require("ui/core/proxy"); + +function validateArgs(element: view.View): view.View { + if (!element) { + throw new Error("element cannot be null or undefinied."); + } + return element; +} + +export module GridUnitType { + export var auto: string = "auto"; + export var pixel: string = "pixel"; + export var star: string = "star"; +} + +export class ItemSpec extends bindable.Bindable implements definition.ItemSpec { + + private _value: number; + private _unitType: string; + + constructor() { + super(); + + if (arguments.length === 0) { + this._value = 1; + this._unitType = GridUnitType.star; + + } + else if (arguments.length === 2) { + if (types.isNumber(arguments[0]) && types.isString(arguments[1])) { + if (arguments[0] < 0 || (arguments[1] !== GridUnitType.auto && arguments[1] !== GridUnitType.star && arguments[1] !== GridUnitType.pixel)) { + throw new Error("Invalid values."); + } + this._value = arguments[0]; + this._unitType = arguments[1]; + } + else { + throw new Error("Arguments must be number and string."); + } + } + else { + throw new Error("ItemSpec expects 0 or 2 arguments"); + } + + this.index = -1; + } + + public owner: GridLayout; + public index: number; + public _actualLength: number = 0; + + public get actualLength(): number { + return this._actualLength; + } + public set actualLength(value: number) { + throw new Error("actualLength is read-only property"); + } + + public static equals(value1: ItemSpec, value2: ItemSpec): boolean { + return (value1.gridUnitType === value2.gridUnitType) && (value1.value === value2.value) && (value1.owner === value2.owner) && (value1.index === value2.index); + } + + get gridUnitType(): string { + return this._unitType; + } + + get isAbsolute(): boolean { + return this._unitType === GridUnitType.pixel; + } + + get isAuto(): boolean { + return this._unitType === GridUnitType.auto; + } + + get isStar(): boolean { + return this._unitType === GridUnitType.star; + } + + get value(): number { + return this._value; + } +} + +export class GridLayout extends layouts.LayoutBase implements definition.GridLayout, view.ApplyXmlAttributes { + private _rows: Array = new Array(); + private _cols: Array = new Array(); + protected _singleRow: ItemSpec = new ItemSpec(); + protected _singleColumn: ItemSpec = new ItemSpec(); + + public static columnProperty = new dependencyObservable.Property("Column", "GridLayout", + new proxy.PropertyMetadata(0, dependencyObservable.PropertyMetadataSettings.None, GridLayout.onColumnPropertyChanged, numberUtils.notNegative)); + + public static columnSpanProperty = new dependencyObservable.Property("ColumnSpan", "GridLayout", + new proxy.PropertyMetadata(1, dependencyObservable.PropertyMetadataSettings.None, GridLayout.onColumnSpanPropertyChanged, numberUtils.greaterThanZero)); + + public static rowProperty = new dependencyObservable.Property("Row", "GridLayout", + new proxy.PropertyMetadata(0, dependencyObservable.PropertyMetadataSettings.None, GridLayout.onRowPropertyChanged, numberUtils.notNegative)); + + public static rowSpanProperty = new dependencyObservable.Property("RowSpan", "GridLayout", + new proxy.PropertyMetadata(1, dependencyObservable.PropertyMetadataSettings.None, GridLayout.onRowSpanPropertyChanged, numberUtils.greaterThanZero)); + + public static getColumn(element: view.View): number { + return validateArgs(element)._getValue(GridLayout.columnProperty); + } + + public static setColumn(element: view.View, value: number): void { + validateArgs(element)._setValue(GridLayout.columnProperty, value); + } + + public static getColumnSpan(element: view.View): number { + return validateArgs(element)._getValue(GridLayout.columnSpanProperty); + } + + public static setColumnSpan(element: view.View, value: number): void { + validateArgs(element)._setValue(GridLayout.columnSpanProperty, value); + } + + public static getRow(element: view.View): number { + return validateArgs(element)._getValue(GridLayout.rowProperty); + } + + public static setRow(element: view.View, value: number): void { + validateArgs(element)._setValue(GridLayout.rowProperty, value); + } + + public static getRowSpan(element: view.View): number { + return validateArgs(element)._getValue(GridLayout.rowSpanProperty); + } + + public static setRowSpan(element: view.View, value: number): void { + validateArgs(element)._setValue(GridLayout.rowSpanProperty, value); + } + + constructor() { + super(); + this._singleRow.index = 0 + this._singleColumn.index = 0; + } + + public addRow(itemSpec: ItemSpec) { + GridLayout.validateItemSpec(itemSpec); + itemSpec.owner = this; + this._rows.push(itemSpec); + this.onRowAdded(itemSpec); + } + + public addColumn(itemSpec: ItemSpec) { + GridLayout.validateItemSpec(itemSpec); + itemSpec.owner = this; + this._cols.push(itemSpec); + this.onColumnAdded(itemSpec); + } + + public removeRow(itemSpec: ItemSpec): void { + if (!itemSpec) { + throw new Error("Value is null."); + } + + var index = this._rows.indexOf(itemSpec); + if (itemSpec.owner !== this || index < 0) { + throw new Error("Row is not child of this GridLayout"); + } + + itemSpec.index = -1; + this._rows.splice(index, 1); + this.onRowRemoved(itemSpec, index); + } + + public removeColumn(itemSpec: ItemSpec): void { + if (!itemSpec) { + throw new Error("Value is null."); + } + + var index = this._cols.indexOf(itemSpec); + if (itemSpec.owner !== this || index < 0) { + throw new Error("Column is not child of this GridLayout"); + } + + itemSpec.index = -1; + this._cols.splice(index, 1); + this.onColumnRemoved(itemSpec, index); + } + + protected onRowChanged(element: view.View, oldValue: number, newValue: number) { + // + } + + protected onRowSpanChanged(element: view.View, oldValue: number, newValue: number) { + // + } + + protected onColumnChanged(element: view.View, oldValue: number, newValue: number) { + // + } + + protected onColumnSpanChanged(element: view.View, oldValue: number, newValue: number) { + // + } + + protected onRowAdded(itemSpec: ItemSpec) { + // + } + + protected onColumnAdded(itemSpec: ItemSpec) { + // + } + + protected onRowRemoved(itemSpec: ItemSpec, index: number) { + // + } + + protected onColumnRemoved(itemSpec: ItemSpec, index: number) { + // + } + + public getColumns(): Array { + return this._cols.slice(); + } + + public getRows(): Array { + return this._rows.slice(); + } + + protected getColumn(view: view.View): ItemSpec { + if (this._cols.length === 0) { + return this._singleColumn; + } + + var columnIndex = Math.min(GridLayout.getColumn(view), this._cols.length - 1); + return this._cols[columnIndex]; + } + + protected getRow(view: view.View): ItemSpec { + if (this._rows.length === 0) { + return this._singleRow; + } + + var columnIndex = Math.min(GridLayout.getRow(view), this._rows.length - 1); + return this._rows[columnIndex]; + } + + protected getColumnSpan(view: view.View, columnIndex: number): number { + if (this._cols.length === 0) { + return 1; + } + + return Math.min(GridLayout.getColumnSpan(view), this._cols.length - columnIndex); + } + + protected getRowSpan(view: view.View, rowIndex: number): number { + if (this._rows.length === 0) { + return 1; + } + + return Math.min(GridLayout.getRowSpan(view), this._rows.length - rowIndex); + } + + protected invalidate(): void { + // + } + + applyXmlAttribute(attributeName: string, attributeValue: any): boolean { + if (attributeName === "columns") { + this.setColumns(attributeValue); + return true; + } + else if (attributeName === "rows") { + this.setRows(attributeValue); + return true; + } + + return false; + } + + private static parseItemSpecs(value: string): Array { + var result = new Array(); + var arr = value.split(","); + for (var i = 0; i < arr.length; i++) { + result.push(GridLayout.convertGridLength(arr[i].trim())); + } + + return result; + } + + private static convertGridLength(value: string): ItemSpec { + + if (value === "auto") { + return new definition.ItemSpec(1, definition.GridUnitType.auto); + } + else if (value.indexOf("*") !== -1) { + var starCount = parseInt(value.replace("*", "") || "1"); + return new definition.ItemSpec(starCount, definition.GridUnitType.star); + } + else if (!isNaN(parseInt(value))) { + return new definition.ItemSpec(parseInt(value), definition.GridUnitType.pixel); + } + else { + throw new Error("Cannot parse item spec from string: " + value); + } + } + + private static onRowPropertyChanged(data: dependencyObservable.PropertyChangeData): void { + var element = GridLayout.getView(data.object); + var grid = element.parent; + if (grid instanceof GridLayout) { + grid.onRowChanged(element, data.oldValue, data.newValue); + } + } + + private static onColumnPropertyChanged(data: dependencyObservable.PropertyChangeData): void { + var element = GridLayout.getView(data.object); + var grid = element.parent; + if (grid instanceof GridLayout) { + grid.onColumnChanged(element, data.oldValue, data.newValue); + } + } + + private static onRowSpanPropertyChanged(data: dependencyObservable.PropertyChangeData): void { + var element = GridLayout.getView(data.object); + var grid = element.parent; + if (grid instanceof GridLayout) { + grid.onRowSpanChanged(element, data.oldValue, data.newValue); + } + } + + private static onColumnSpanPropertyChanged(data: dependencyObservable.PropertyChangeData): void { + var element = GridLayout.getView(data.object); + var grid = element.parent; + if (grid instanceof GridLayout) { + grid.onColumnSpanChanged(element, data.oldValue, data.newValue); + } + } + + private static validateItemSpec(itemSpec: ItemSpec): void { + if (!itemSpec) { + throw new Error("Value cannot be undefined."); + } + + if (itemSpec.owner) { + throw new Error("itemSpec is already added to GridLayout."); + } + } + + private static getView(object: Object): view.View { + if (object instanceof view.View) { + return object; + } + + throw new Error("Element is not View or its descendant."); + } + + private setColumns(value: string) { + this._cols = GridLayout.parseItemSpecs(value); + this.invalidate(); + } + + private setRows(value: string) { + this._rows = GridLayout.parseItemSpecs(value); + this.invalidate(); + } +} \ No newline at end of file diff --git a/ui/layouts/grid-layout/grid-layout.android.ts b/ui/layouts/grid-layout/grid-layout.android.ts new file mode 100644 index 000000000..c420b1725 --- /dev/null +++ b/ui/layouts/grid-layout/grid-layout.android.ts @@ -0,0 +1,111 @@ +import definition = require("ui/layouts/grid-layout"); +import utils = require("utils/utils"); +import dependencyObservable = require("ui/core/dependency-observable"); +import enums = require("ui/enums"); +import view = require("ui/core/view"); +import bindable = require("ui/core/bindable"); +import types = require("utils/types"); +import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/grid-layout/grid-layout-common"); + +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); + + +function setNativeProperty(data: dependencyObservable.PropertyChangeData, setter: (lp: org.nativescript.widgets.CommonLayoutParams) => void) { + + var uiView = data.object; + if (uiView instanceof view.View) { + var nativeView: android.view.View = uiView._nativeView; + + var lp = nativeView.getLayoutParams(); + if (!(lp instanceof org.nativescript.widgets.CommonLayoutParams)) { + lp = new org.nativescript.widgets.CommonLayoutParams(); + } + setter(lp); + nativeView.setLayoutParams(lp); + } +} + +function setNativeRowProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.row = data.newValue; }); +} + +function setNativeRowSpanProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.rowSpan = data.newValue; }); +} + +function setNativeColumnProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.column = data.newValue; }); +} + +function setNativeColumnSpanProperty(data: dependencyObservable.PropertyChangeData) { + setNativeProperty(data, (lp) => { lp.columnSpan = data.newValue; }); +} + +(common.GridLayout.rowProperty.metadata).onSetNativeValue = setNativeRowProperty; +(common.GridLayout.rowSpanProperty.metadata).onSetNativeValue = setNativeRowSpanProperty; +(common.GridLayout.columnProperty.metadata).onSetNativeValue = setNativeColumnProperty; +(common.GridLayout.columnSpanProperty.metadata).onSetNativeValue = setNativeColumnSpanProperty; + +function createNativeSpec(itemSpec: common.ItemSpec): org.nativescript.widgets.ItemSpec { + switch (itemSpec.gridUnitType) { + case common.GridUnitType.auto: + return new org.nativescript.widgets.ItemSpec(itemSpec.value, org.nativescript.widgets.GridUnitType.auto); + + case common.GridUnitType.star: + return new org.nativescript.widgets.ItemSpec(itemSpec.value, org.nativescript.widgets.GridUnitType.star); + + case common.GridUnitType.pixel: + return new org.nativescript.widgets.ItemSpec(itemSpec.value * utils.layout.getDisplayDensity(), org.nativescript.widgets.GridUnitType.pixel); + + default: + throw new Error("Invalid gridUnitType: " + itemSpec.gridUnitType); + } +} + +export class GridLayout extends common.GridLayout { + + private _layout: org.nativescript.widgets.GridLayout; + + get android(): org.nativescript.widgets.GridLayout { + return this._layout; + } + + get _nativeView(): org.nativescript.widgets.GridLayout { + return this._layout; + } + + public _createUI() { + this._layout = new org.nativescript.widgets.GridLayout(this._context); + + // Update native GridLayout + this.getRows().forEach((itemSpec, index, rows) => { this.onRowAdded(itemSpec); }, this); + this.getColumns().forEach((itemSpec, index, rows) => { this.onColumnAdded(itemSpec); }, this); + } + + protected onRowAdded(itemSpec: common.ItemSpec) { + if (this._layout) { + this._layout.addRow(createNativeSpec(itemSpec)); + } + } + + protected onColumnAdded(itemSpec: common.ItemSpec) { + if (this._layout) { + this._layout.addColumn(createNativeSpec(itemSpec)); + } + } + + protected onRowRemoved(itemSpec: common.ItemSpec, index: number) { + if (this._layout) { + this._layout.removeRowAt(index); + } + } + + protected onColumnRemoved(itemSpec: common.ItemSpec, index: number) { + if (this._layout) { + this._layout.removeColumnAt(index); + } + } +} \ No newline at end of file diff --git a/ui/layouts/grid-layout/grid-layout.d.ts b/ui/layouts/grid-layout/grid-layout.d.ts index 2b2e95e0c..af688603f 100644 --- a/ui/layouts/grid-layout/grid-layout.d.ts +++ b/ui/layouts/grid-layout/grid-layout.d.ts @@ -1,5 +1,5 @@ declare module "ui/layouts/grid-layout" { - import layout = require("ui/layouts/layout"); + import layout = require("ui/layouts/layout-base"); import view = require("ui/core/view"); /** @@ -68,7 +68,7 @@ /** * Defines a rectangular layout area that consists of columns and rows. */ - export class GridLayout extends layout.Layout { + export class GridLayout extends layout.LayoutBase { ///** // * Initializes a new instance of GridLayout. diff --git a/ui/layouts/grid-layout/grid-layout.ts b/ui/layouts/grid-layout/grid-layout.ios.ts similarity index 73% rename from ui/layouts/grid-layout/grid-layout.ts rename to ui/layouts/grid-layout/grid-layout.ios.ts index df8bc5727..6cc34df8c 100644 --- a/ui/layouts/grid-layout/grid-layout.ts +++ b/ui/layouts/grid-layout/grid-layout.ios.ts @@ -1,295 +1,54 @@ -import layouts = require("ui/layouts/layout"); -import definition = require("ui/layouts/grid-layout"); -import utils = require("utils/utils"); -import dependencyObservable = require("ui/core/dependency-observable"); +import utils = require("utils/utils"); import enums = require("ui/enums"); import view = require("ui/core/view"); -import bindable = require("ui/core/bindable"); -import types = require("utils/types"); -import numberUtils = require("utils/number-utils"); +import common = require("ui/layouts/grid-layout/grid-layout-common"); -export module GridUnitType { - export var auto: string = "auto"; - export var pixel: string = "pixel"; - export var star: string = "star"; -} +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); -export class ItemSpec extends bindable.Bindable implements definition.ItemSpec { - - private _value: number; - private _unitType: string; - - constructor() { - super(); - - if (arguments.length === 0) { - this._value = 1; - this._unitType = GridUnitType.star; - - } - else if (arguments.length === 2) { - if (types.isNumber(arguments[0]) && types.isString(arguments[1])) { - if (arguments[0] < 0 || (arguments[1] !== GridUnitType.auto && arguments[1] !== GridUnitType.star && arguments[1] !== GridUnitType.pixel)) { - throw new Error("Invalid values."); - } - this._value = arguments[0]; - this._unitType = arguments[1]; - } - else { - throw new Error("Arguments must be number and string."); - } - } - else { - throw new Error("ItemSpec expects 0 or 2 arguments"); - } - - this.index = -1; - } - - public owner: GridLayout; - public index: number; - public _actualLength: number = 0; - - public get actualLength(): number { - return this._actualLength; - } - public set actualLength(value: number) { - throw new Error("actualLength is read-only property"); - } - - public static equals(value1: ItemSpec, value2: ItemSpec): boolean { - return (value1.gridUnitType === value2.gridUnitType) && (value1.value === value2.value) && (value1.owner === value2.owner) && (value1.index === value2.index); - } - - get gridUnitType(): string { - return this._unitType; - } - - get isAbsolute(): boolean { - return this._unitType === GridUnitType.pixel; - } - - get isAuto(): boolean { - return this._unitType === GridUnitType.auto; - } - - get isStar(): boolean { - return this._unitType === GridUnitType.star; - } - - get value(): number { - return this._value; - } -} - -export class GridLayout extends layouts.Layout implements definition.GridLayout, view.ApplyXmlAttributes { - private _rows: Array = new Array(); - private _cols: Array = new Array(); - private _singleRow: ItemSpec = new ItemSpec(); - private _singleColumn: ItemSpec = new ItemSpec(); +export class GridLayout extends common.GridLayout { private _isValid: boolean = false; - private helper: MeasureHelper; - public static columnProperty = new dependencyObservable.Property("Column", "GridLayout", - new dependencyObservable.PropertyMetadata(0, dependencyObservable.PropertyMetadataSettings.None, GridLayout.attachedPropertyChanged, numberUtils.notNegative)); - public static columnSpanProperty = new dependencyObservable.Property("ColumnSpan", "GridLayout", - new dependencyObservable.PropertyMetadata(1, dependencyObservable.PropertyMetadataSettings.None, GridLayout.attachedPropertyChanged, numberUtils.greaterThanZero)); - - public static rowProperty = new dependencyObservable.Property("Row", "GridLayout", - new dependencyObservable.PropertyMetadata(0, dependencyObservable.PropertyMetadataSettings.None, GridLayout.attachedPropertyChanged, numberUtils.notNegative)); - public static rowSpanProperty = new dependencyObservable.Property("RowSpan", "GridLayout", - new dependencyObservable.PropertyMetadata(1, dependencyObservable.PropertyMetadataSettings.None, GridLayout.attachedPropertyChanged, numberUtils.greaterThanZero)); - - public static getColumn(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); + protected onRowAdded(itemSpec: common.ItemSpec) { + this.invalidate(); } - return element._getValue(GridLayout.columnProperty); - } - - public static setColumn(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(GridLayout.columnProperty, value); - } - - public static getColumnSpan(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - return element._getValue(GridLayout.columnSpanProperty); - } - - public static setColumnSpan(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(GridLayout.columnSpanProperty, value); - } - - public static getRow(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - return element._getValue(GridLayout.rowProperty); - } - - public static setRow(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(GridLayout.rowProperty, value); - } - - public static getRowSpan(element: view.View): number { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - return element._getValue(GridLayout.rowSpanProperty); - } - - public static setRowSpan(element: view.View, value: number): void { - if (!element) { - throw new Error("element cannot be null or undefinied."); - } - element._setValue(GridLayout.rowSpanProperty, value); - } - - private static attachedPropertyChanged(data: dependencyObservable.PropertyChangeData): void { - if (data.object instanceof view.View) { - var element = data.object; - if (!element) { - throw new Error("Element is not View."); - } - - var grid = element.parent; - if (grid instanceof GridLayout) { - grid.invalidate(); - } - } - } - - private static validateItemSpec(itemSpec: ItemSpec): void { - if (!itemSpec) { - throw new Error("Value cannot be undefined."); - } - - if (itemSpec.owner) { - throw new Error("itemSpec is already added to GridLayout."); - } - } - - constructor() { - super(); - this._singleRow.index = 0 - this._singleColumn.index = 0; - } - - public addRow(itemSpec: ItemSpec) { - GridLayout.validateItemSpec(itemSpec); - itemSpec.owner = this; - this._rows.push(itemSpec); + protected onColumnAdded(itemSpec: common.ItemSpec) { this.invalidate(); } - public addColumn(itemSpec: ItemSpec) { - GridLayout.validateItemSpec(itemSpec); - itemSpec.owner = this; - this._cols.push(itemSpec); + protected onRowRemoved(itemSpec: common.ItemSpec, index: number) { this.invalidate(); } - public removeColumn(itemSpec: ItemSpec): void { - if (!itemSpec) { - throw new Error("Value is null."); - } - - var index = this._cols.indexOf(itemSpec); - if (itemSpec.owner !== this || index < 0) { - throw new Error("Column is not child of this GridLayout"); - } - - itemSpec.index = -1; - this._cols.splice(index, 1); + protected onColumnRemoved(itemSpec: common.ItemSpec, index: number) { this.invalidate(); } - public removeRow(itemSpec: ItemSpec): void { - if (!itemSpec) { - throw new Error("Value is null."); - } - - var index = this._rows.indexOf(itemSpec); - if (itemSpec.owner !== this || index < 0) { - throw new Error("Row is not child of this GridLayout"); - } - - itemSpec.index = -1; - this._rows.splice(index, 1); + protected onRowChanged(element: view.View, oldValue: number, newValue: number) { this.invalidate(); } - public getColumns(): Array { - return this._cols.slice(); - } - - public getRows(): Array { - return this._rows.slice(); - } - - private setColumns(value: string) { - this._cols = GridLayout.parseItemSpecs(value); + protected onRowSpanChanged(element: view.View, oldValue: number, newValue: number) { this.invalidate(); } - private setRows(value: string) { - this._rows = GridLayout.parseItemSpecs(value); + protected onColumnChanged(element: view.View, oldValue: number, newValue: number) { this.invalidate(); } - public invalidate(): void { + protected onColumnSpanChanged(element: view.View, oldValue: number, newValue: number) { + this.invalidate(); + } + + protected invalidate(): void { this._isValid = false; this.requestLayout(); } - private getColumn(view: view.View): ItemSpec { - if (this._cols.length === 0) { - return this._singleColumn; - } - - var columnIndex = Math.min(GridLayout.getColumn(view), this._cols.length - 1); - return this._cols[columnIndex]; - } - - private getRow(view: view.View): ItemSpec { - if (this._rows.length === 0) { - return this._singleRow; - } - - var columnIndex = Math.min(GridLayout.getRow(view), this._rows.length - 1); - return this._rows[columnIndex]; - } - - private getColumnSpan(view: view.View, columnIndex: number): number { - if (this._cols.length === 0) { - return 1; - } - - return Math.min(GridLayout.getColumnSpan(view), this._cols.length - columnIndex); - } - - private getRowSpan(view: view.View, rowIndex: number): number { - if (this._rows.length === 0) { - return 1; - } - - return Math.min(GridLayout.getRowSpan(view), this._rows.length - rowIndex); - } - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -306,17 +65,20 @@ export class GridLayout extends layouts.Layout implements definition.GridLayout, var infinityWidth = widthMode === utils.layout.UNSPECIFIED; var infinityHeight = heightMode === utils.layout.UNSPECIFIED; - var column: ItemSpec; + var column: common.ItemSpec; var columnGroup: ColumnGroup; - var row: ItemSpec; + var row: common.ItemSpec; var rowGroup: RowGroup; + + var rows = this.getRows(); + var cols = this.getColumns(); if (!this._isValid) { - this._rows.forEach((value: ItemSpec, index: number, array: ItemSpec[]) => { + rows.forEach((value: common.ItemSpec, index: number, array: common.ItemSpec[]) => { value.index = index; }); - this._cols.forEach((value: ItemSpec, index: number, array: ItemSpec[]) => { + cols.forEach((value: common.ItemSpec, index: number, array: common.ItemSpec[]) => { value.index = index; }); @@ -327,8 +89,8 @@ export class GridLayout extends layouts.Layout implements definition.GridLayout, this.helper.infinityWidth = infinityWidth; this.helper.infinityHeight = infinityHeight; - for (i = 0; i < this._cols.length; i++) { - column = this._cols[i]; + for (i = 0; i < cols.length; i++) { + column = cols[i]; columnGroup = new ColumnGroup(column, this.helper); this.helper.columns.push(columnGroup); if (column.isAbsolute) { @@ -336,8 +98,8 @@ export class GridLayout extends layouts.Layout implements definition.GridLayout, } } - for (i = 0; i < this._rows.length; i++) { - row = this._rows[i]; + for (i = 0; i < rows.length; i++) { + row = rows[i]; rowGroup = new RowGroup(row, this.helper); this.helper.rows.push(rowGroup); if (row.isAbsolute) { @@ -345,11 +107,11 @@ export class GridLayout extends layouts.Layout implements definition.GridLayout, } } - if (this._rows.length === 0) { + if (rows.length === 0) { this.helper.rows.push(new RowGroup(this._singleRow, this.helper)); } - if (this._cols.length === 0) { + if (cols.length === 0) { this.helper.columns.push(new ColumnGroup(this._singleColumn, this.helper)); } } @@ -445,44 +207,6 @@ export class GridLayout extends layouts.Layout implements definition.GridLayout, } } } - - _applyXmlAttribute(attributeName: string, attributeValue: any): boolean { - if (attributeName === "columns") { - this.setColumns(attributeValue); - return true; - } - else if (attributeName === "rows") { - this.setRows(attributeValue); - return true; - } - - return super._applyXmlAttribute(attributeName, attributeValue); - } - - private static parseItemSpecs(value: string): Array { - var result = new Array(); - var arr = value.split(","); - for (var i = 0; i < arr.length; i++) { - result.push(GridLayout.convertGridLength(arr[i].trim())); - } - - return result; - } - - private static convertGridLength(value: string): ItemSpec { - - if (value === "auto") { - return new definition.ItemSpec(1, definition.GridUnitType.auto); - } else if (value.indexOf("*") !== -1) { - var starCount = parseInt(value.replace("*", "") || "1"); - return new definition.ItemSpec(starCount, definition.GridUnitType.star); - } else if (!isNaN(parseInt(value))) { - return new definition.ItemSpec(parseInt(value), definition.GridUnitType.pixel); - } - else { - throw new Error("Cannot parse item spec from string: " + value); - } - } } class MeasureSpecs { @@ -503,8 +227,8 @@ class MeasureSpecs { constructor( public child: view.View, - public column: ItemSpec, - public row: ItemSpec, + public column: common.ItemSpec, + public row: common.ItemSpec, columnSpan?: number, rowSpan?: number) { // cannot have zero colSpan. @@ -546,14 +270,14 @@ class MeasureSpecs { class ColumnGroup { width: number = 0; measuredCount = 0; - column: ItemSpec; + column: common.ItemSpec; children: Array = new Array(); owner: MeasureHelper; public measureToFix: number = 0; public currentMeasureToFixCount: number = 0; - constructor(column: ItemSpec, owner: MeasureHelper) { + constructor(column: common.ItemSpec, owner: MeasureHelper) { this.owner = owner; this.column = column; } @@ -587,14 +311,14 @@ class ColumnGroup { class RowGroup { height: number = 0; measuredCount = 0; - row: ItemSpec; + row: common.ItemSpec; children: Array = new Array(); owner: MeasureHelper; public measureToFix: number = 0; public currentMeasureToFixCount: number = 0; - constructor(row: ItemSpec, owner: MeasureHelper) { + constructor(row: common.ItemSpec, owner: MeasureHelper) { this.row = row; this.owner = owner; } diff --git a/ui/layouts/layout-base.d.ts b/ui/layouts/layout-base.d.ts new file mode 100644 index 000000000..316e54cc2 --- /dev/null +++ b/ui/layouts/layout-base.d.ts @@ -0,0 +1,60 @@ +declare module "ui/layouts/layout-base" { + import view = require("ui/core/view"); + import dependencyObservable = require("ui/core/dependency-observable"); + + /** + * Base class for all views that supports children positioning. + */ + export class LayoutBase extends view.CustomLayoutView { + + public static clipToBoundsProperty: dependencyObservable.Property; + + /** + * Returns the number of children in this Layout. + */ + getChildrenCount(): number; + + /** + * Returns the view at the specified position. + * @param index The position at which to get the child from. + */ + getChildAt(index: number): view.View; + + /** + * Adds the view to children array. + * @param view The view to be added to the end of the children array. + */ + addChild(view: view.View); + + /** + * Removes the specified view from the children array. + * @param view The view to remove from the children array. + */ + removeChild(view: view.View); + + /** + * Gets or sets padding style property. + */ + padding: string; + + /** + * Specify the bottom padding of this layout. + */ + paddingBottom: number; + + /** + * Specify the left padding of this layout. + */ + paddingLeft: number; + + /** + * Specify the right padding of this layout. + */ + paddingRight: number; + + /** + * Specify the top padding of this layout. + */ + paddingTop: number; + } +} \ No newline at end of file diff --git a/ui/layouts/layout.ts b/ui/layouts/layout-base.ts similarity index 69% rename from ui/layouts/layout.ts rename to ui/layouts/layout-base.ts index c22ee5e01..525cbd493 100644 --- a/ui/layouts/layout.ts +++ b/ui/layouts/layout-base.ts @@ -1,31 +1,12 @@ -import definition = require("ui/layouts/layout"); +import definition = require("ui/layouts/layout-base"); import view = require("ui/core/view"); import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); -function onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData) { - var nativeView = (data.object)._nativeView; - if (!nativeView) { - return; - } - var value = data.newValue; +export class LayoutBase extends view.CustomLayoutView implements definition.LayoutBase, view.AddChildFromBuilder { - if (nativeView instanceof UIView) { - (nativeView).clipsToBounds = value; - } - else if (nativeView instanceof android.view.ViewGroup) { - (nativeView).setClipChildren(value); - } -} - -var clipToBoundsProperty = new dependencyObservable.Property( - "clipToBounds", - "Layout", - new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, onClipToBoundsPropertyChanged) - ); - -export class Layout extends view.CustomLayoutView implements definition.Layout, view.AddChildFromBuilder { - public static clipToBoundsProperty = clipToBoundsProperty; + public static clipToBoundsProperty = new dependencyObservable.Property("clipToBounds", "LayoutBase", + new proxy.PropertyMetadata(true, dependencyObservable.PropertyMetadataSettings.None, LayoutBase.onClipToBoundsPropertyChanged)); private _subViews: Array = new Array(); @@ -88,6 +69,13 @@ export class Layout extends view.CustomLayoutView implements definition.Layout, } } + get padding(): string { + return this.style.padding; + } + set padding(value: string) { + this.style.padding = value; + } + public get paddingTop(): number { return this.style.paddingTop; } @@ -116,10 +104,12 @@ export class Layout extends view.CustomLayoutView implements definition.Layout, this.style.paddingLeft = value; } - get clipToBounds(): boolean { - return this._getValue(Layout.clipToBoundsProperty); + protected onClipToBoundsChanged(oldValue: boolean, newValue: boolean) { + // + } + + private static onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData): void { + var layout = data.object; + layout.onClipToBoundsChanged(data.oldValue, data.newValue); } - set clipToBounds(value: boolean) { - this._setValue(Layout.clipToBoundsProperty, value); - } -} +} \ No newline at end of file diff --git a/ui/layouts/layout.android.ts b/ui/layouts/layout.android.ts new file mode 100644 index 000000000..ef95d1f44 --- /dev/null +++ b/ui/layouts/layout.android.ts @@ -0,0 +1,72 @@ +import definition = require("ui/layouts/layout"); +import view = require("ui/core/view"); +import layoutBase = require("ui/layouts/layout-base"); +import trace = require("trace"); +import utils = require("utils/utils"); +import proxy = require("ui/core/proxy"); + +var OWNER = "_owner"; + +export class Layout extends layoutBase.LayoutBase implements definition.Layout { + private _viewGroup: android.view.ViewGroup; + + get android(): android.view.ViewGroup { + return this._viewGroup; + } + + get _nativeView(): android.view.ViewGroup { + return this._viewGroup; + } + + public _createUI() { + this._viewGroup = new view.NativeViewGroup(this._context); + this._viewGroup[OWNER] = this; + } + + public _onDetached(force?: boolean) { + delete this._viewGroup[OWNER]; + super._onDetached(force); + } + + public measure(widthMeasureSpec: number, heightMeasureSpec: number): void { + this._setCurrentMeasureSpecs(widthMeasureSpec, heightMeasureSpec); + + var view = this._nativeView; + if (view) { + var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); + var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); + + var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); + var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); + + trace.write(this + " :measure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); + view.measure(widthMeasureSpec, heightMeasureSpec); + } + } + + public layout(left: number, top: number, right: number, bottom: number): void { + this._setCurrentLayoutBounds(left, top, right, bottom); + + var view = this._nativeView; + if (view) { + this.layoutNativeView(left, top, right, bottom); + trace.write(this + " :layout: " + left + ", " + top + ", " + (right - left) + ", " + (bottom - top), trace.categories.Layout); + } + } + + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + // Don't call super because it will trigger measure again. + + var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); + var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); + + var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); + var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); + trace.write(this + " :onMeasure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); + } + + public onLayout(left: number, top: number, right: number, bottom: number): void { + // Don't call super because it will trigger layout again. + trace.write(this + " :onLayout: " + left + ", " + top + ", " + (right - left) + ", " + (bottom - top), trace.categories.Layout); + } +} \ No newline at end of file diff --git a/ui/layouts/layout.d.ts b/ui/layouts/layout.d.ts index b30e85dda..011f74b9c 100644 --- a/ui/layouts/layout.d.ts +++ b/ui/layouts/layout.d.ts @@ -1,65 +1,11 @@ declare module "ui/layouts/layout" { import view = require("ui/core/view"); + import layoutBase = require("ui/layouts/layout-base"); /** - * Base class for all views that supports children positioning. + * Base class for all views that supports children positioning in cross platform manner. */ - export class Layout extends view.View { - - /** - * Returns the number of children in this Layout. - */ - getChildrenCount(): number; - - /** - * Returns the view at the specified position. - * @param index The position at which to get the child from. - */ - getChildAt(index: number): view.View; - - /** - * Returns the position of the child view - * @param child The child view that we are looking for. - */ - getChildIndex(child: view.View): number; - - /** - * Adds the view to children array. - * @param view The view to be added to the end of the children array. - */ - addChild(view: view.View); - - /** - * Inserts the view to children array at the specified index. - * @param atIndex The insertion index. - * @param view The view to be added to the end of the children array. - */ - insertChild(atIndex: number, child: view.View); - - /** - * Removes the specified view from the children array. - * @param view The view to remove from the children array. - */ - removeChild(view: view.View); - - /** - * Specify the bottom padding of this layout. - */ - paddingBottom: number; - - /** - * Specify the left padding of this layout. - */ - paddingLeft: number; - - /** - * Specify the right padding of this layout. - */ - paddingRight: number; - - /** - * Specify the top padding of this layout. - */ - paddingTop: number; + export class Layout extends layoutBase.LayoutBase { + // } -} +} \ No newline at end of file diff --git a/ui/layouts/layout.ios.ts b/ui/layouts/layout.ios.ts new file mode 100644 index 000000000..618c4597c --- /dev/null +++ b/ui/layouts/layout.ios.ts @@ -0,0 +1,41 @@ +import definition = require("ui/layouts/layout"); +import layoutBase = require("ui/layouts/layout-base"); +import trace = require("trace"); +import utils = require("utils/utils"); + +export class Layout extends layoutBase.LayoutBase implements definition.Layout { + + private _view: UIView; + + constructor() { + super(); + + this._view = new UIView(); + this._view.autoresizesSubviews = false; + } + + get ios(): UIView { + return this._view; + } + + get _nativeView(): UIView { + return this._view; + } + + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + // Don't call super because it will measure the native element. + + var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); + var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); + + var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); + var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); + trace.write(this + " :onMeasure: " + utils.layout.getMode(widthMode) + " " + width + ", " + utils.layout.getMode(heightMode) + " " + height, trace.categories.Layout); + } + + protected onClipToBoundsChanged(oldValue: boolean, newValue: boolean): void { + if (this._nativeView) { + this._nativeView.clipsToBounds = newValue; + } + } +} \ No newline at end of file diff --git a/ui/layouts/stack-layout/stack-layout-common.ts b/ui/layouts/stack-layout/stack-layout-common.ts new file mode 100644 index 000000000..da71dc3d1 --- /dev/null +++ b/ui/layouts/stack-layout/stack-layout-common.ts @@ -0,0 +1,25 @@ +import layouts = require("ui/layouts/layout-base"); +import definition = require("ui/layouts/stack-layout"); +import dependencyObservable = require("ui/core/dependency-observable"); +import enums = require("ui/enums"); +import proxy = require("ui/core/proxy"); + +// on Android we explicitly set propertySettings to None because android will invalidate its layout (skip unnecessary native call). +var AffectsLayout = global.android ? dependencyObservable.PropertyMetadataSettings.None : dependencyObservable.PropertyMetadataSettings.AffectsLayout; + +function validateOrientation(value: any): boolean { + return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; +} + +export class StackLayout extends layouts.LayoutBase implements definition.StackLayout { + + public static orientationProperty = new dependencyObservable.Property("orientation","StackLayout", + new proxy.PropertyMetadata(enums.Orientation.vertical, AffectsLayout, undefined, validateOrientation)); + + get orientation(): string { + return this._getValue(StackLayout.orientationProperty); + } + set orientation(value: string) { + this._setValue(StackLayout.orientationProperty, value); + } +} \ No newline at end of file diff --git a/ui/layouts/stack-layout/stack-layout.android.ts b/ui/layouts/stack-layout/stack-layout.android.ts new file mode 100644 index 000000000..e92ea19a1 --- /dev/null +++ b/ui/layouts/stack-layout/stack-layout.android.ts @@ -0,0 +1,38 @@ +import dependencyObservable = require("ui/core/dependency-observable"); +import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/stack-layout/stack-layout-common"); +import enums = require("ui/enums"); + +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); + +export class StackLayout extends common.StackLayout { + + static setNativeOrientationProperty(data: dependencyObservable.PropertyChangeData): void { + var stackLayout = data.object; + var nativeView = stackLayout._nativeView; + if (data.newValue === enums.Orientation.vertical) { + nativeView.setOrientation(org.nativescript.widgets.Orientation.vertical); + } + else { + nativeView.setOrientation(org.nativescript.widgets.Orientation.horzontal); + } + } + + private _layout: org.nativescript.widgets.StackLayout; + + get android(): org.nativescript.widgets.StackLayout { + return this._layout; + } + + get _nativeView(): org.nativescript.widgets.StackLayout { + return this._layout; + } + + public _createUI() { + this._layout = new org.nativescript.widgets.StackLayout(this._context); + } +} + +(common.StackLayout.orientationProperty.metadata).onSetNativeValue = StackLayout.setNativeOrientationProperty; \ No newline at end of file diff --git a/ui/layouts/stack-layout/stack-layout.d.ts b/ui/layouts/stack-layout/stack-layout.d.ts index 09d5e1e7e..9ce0486e5 100644 --- a/ui/layouts/stack-layout/stack-layout.d.ts +++ b/ui/layouts/stack-layout/stack-layout.d.ts @@ -1,11 +1,11 @@ declare module "ui/layouts/stack-layout" { - import layout = require("ui/layouts/layout"); + import layout = require("ui/layouts/layout-base"); import dependencyObservable = require("ui/core/dependency-observable"); /** * A Layout that arranges its children horizontally or vertically. The direction can be set by orientation property. */ - class StackLayout extends layout.Layout { + class StackLayout extends layout.LayoutBase { /** * Represents the observable property backing the orientation property of each StackLayout instance. diff --git a/ui/layouts/stack-layout/stack-layout.ts b/ui/layouts/stack-layout/stack-layout.ios.ts similarity index 88% rename from ui/layouts/stack-layout/stack-layout.ts rename to ui/layouts/stack-layout/stack-layout.ios.ts index 0da139aa0..79c91ec8b 100644 --- a/ui/layouts/stack-layout/stack-layout.ts +++ b/ui/layouts/stack-layout/stack-layout.ios.ts @@ -1,35 +1,16 @@ -import layouts = require("ui/layouts/layout"); -import definition = require("ui/layouts/stack-layout"); -import utils = require("utils/utils"); -import dependencyObservable = require("ui/core/dependency-observable"); +import utils = require("utils/utils"); import enums = require("ui/enums"); -import proxy = require("ui/core/proxy"); import view = require("ui/core/view"); +import common = require("ui/layouts/stack-layout/stack-layout-common"); -function validateOrientation(value: any): boolean { - return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; -} +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); -export var orientationProperty = new dependencyObservable.Property( - "orientation", - "StackLayout", - new proxy.PropertyMetadata(enums.Orientation.vertical, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - validateOrientation) - ); - -export class StackLayout extends layouts.Layout implements definition.StackLayout { +export class StackLayout extends common.StackLayout { private _totalLength = 0; - get orientation(): string { - return this._getValue(orientationProperty); - } - set orientation(value: string) { - this._setValue(orientationProperty, value); - } - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { super.onMeasure(widthMeasureSpec, heightMeasureSpec); var density = utils.layout.getDisplayDensity(); diff --git a/ui/layouts/wrap-layout/wrap-layout-common.ts b/ui/layouts/wrap-layout/wrap-layout-common.ts new file mode 100644 index 000000000..f9c1579dc --- /dev/null +++ b/ui/layouts/wrap-layout/wrap-layout-common.ts @@ -0,0 +1,49 @@ +import layouts = require("ui/layouts/layout-base"); +import definition = require("ui/layouts/wrap-layout"); +import dependencyObservable = require("ui/core/dependency-observable"); +import enums = require("ui/enums"); +import proxy = require("ui/core/proxy"); + +// on Android we explicitly set propertySettings to None because android will invalidate its layout (so we skip unnecessary native call). +var AffectsLayout = global.android ? dependencyObservable.PropertyMetadataSettings.None : dependencyObservable.PropertyMetadataSettings.AffectsLayout; + +function isWidthHeightValid(value: any): boolean { + return (value >= 0.0 && value !== Number.POSITIVE_INFINITY); +} + +function isValidOrientation(value: any): boolean { + return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; +} + +export class WrapLayout extends layouts.LayoutBase implements definition.WrapLayout { + + public static orientationProperty = new dependencyObservable.Property("orientation", "WrapLayout", + new proxy.PropertyMetadata(enums.Orientation.horizontal, AffectsLayout, undefined, isValidOrientation)); + + public static itemWidthProperty = new dependencyObservable.Property("itemWidth", "WrapLayout", + new proxy.PropertyMetadata(0, AffectsLayout, undefined, isWidthHeightValid)); + + public static itemHeightProperty = new dependencyObservable.Property("itemHeight", "WrapLayout", + new proxy.PropertyMetadata(0, AffectsLayout, undefined, isWidthHeightValid)); + + get orientation(): string { + return this._getValue(WrapLayout.orientationProperty); + } + set orientation(value: string) { + this._setValue(WrapLayout.orientationProperty, value); + } + + get itemWidth(): number { + return this._getValue(WrapLayout.itemWidthProperty); + } + set itemWidth(value: number) { + this._setValue(WrapLayout.itemWidthProperty, value); + } + + get itemHeight(): number { + return this._getValue(WrapLayout.itemHeightProperty); + } + set itemHeight(value: number) { + this._setValue(WrapLayout.itemHeightProperty, value); + } +} \ No newline at end of file diff --git a/ui/layouts/wrap-layout/wrap-layout.android.ts b/ui/layouts/wrap-layout/wrap-layout.android.ts new file mode 100644 index 000000000..0aa0f0585 --- /dev/null +++ b/ui/layouts/wrap-layout/wrap-layout.android.ts @@ -0,0 +1,46 @@ +import dependencyObservable = require("ui/core/dependency-observable"); +import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/wrap-layout/wrap-layout-common"); + +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); + +export class WrapLayout extends common.WrapLayout { + + static setNativeOrientationProperty(data: dependencyObservable.PropertyChangeData): void { + var wrapLayout = data.object; + var nativeView = wrapLayout._nativeView; + nativeView.setOrientation(data.newValue); + } + + static setNativeItemWidthProperty(data: dependencyObservable.PropertyChangeData): void { + var wrapLayout = data.object; + var nativeView = wrapLayout._nativeView; + nativeView.setItemWidth(data.newValue); + } + + static setNativeItemHeightProperty(data: dependencyObservable.PropertyChangeData): void { + var wrapLayout = data.object; + var nativeView = wrapLayout._nativeView; + nativeView.setItemHeight(data.newValue); + } + + private _layout: org.nativescript.widgets.WrapLayout; + + get android(): org.nativescript.widgets.WrapLayout { + return this._layout; + } + + get _nativeView(): org.nativescript.widgets.WrapLayout { + return this._layout; + } + + public _createUI() { + this._layout = new org.nativescript.widgets.WrapLayout(this._context); + } +} + +(common.WrapLayout.orientationProperty.metadata).onSetNativeValue = WrapLayout.setNativeOrientationProperty; +(common.WrapLayout.orientationProperty.metadata).onSetNativeValue = WrapLayout.setNativeItemWidthProperty; +(common.WrapLayout.orientationProperty.metadata).onSetNativeValue = WrapLayout.setNativeItemHeightProperty; \ No newline at end of file diff --git a/ui/layouts/wrap-layout/wrap-layout.d.ts b/ui/layouts/wrap-layout/wrap-layout.d.ts index 156569499..eabab3642 100644 --- a/ui/layouts/wrap-layout/wrap-layout.d.ts +++ b/ui/layouts/wrap-layout/wrap-layout.d.ts @@ -1,12 +1,12 @@ declare module "ui/layouts/wrap-layout" { - import layout = require("ui/layouts/layout"); + import layout = require("ui/layouts/layout-base"); import dependencyObservable = require("ui/core/dependency-observable"); /** * WrapLayout position children in rows or columns depending on orientation property * until space is filled and then wraps them on new row or column. */ - class WrapLayout extends layout.Layout { + class WrapLayout extends layout.LayoutBase { /** * Represents the observable property backing the orientation property of each WrapLayout instance. diff --git a/ui/layouts/wrap-layout/wrap-layout.ts b/ui/layouts/wrap-layout/wrap-layout.ios.ts similarity index 78% rename from ui/layouts/wrap-layout/wrap-layout.ts rename to ui/layouts/wrap-layout/wrap-layout.ios.ts index 3194658dc..ce70f661c 100644 --- a/ui/layouts/wrap-layout/wrap-layout.ts +++ b/ui/layouts/wrap-layout/wrap-layout.ios.ts @@ -1,73 +1,20 @@ -import layouts = require("ui/layouts/layout"); -import definition = require("ui/layouts/wrap-layout"); -import utils = require("utils/utils"); -import dependencyObservable = require("ui/core/dependency-observable"); +import utils = require("utils/utils"); import view = require("ui/core/view"); import enums = require("ui/enums"); +import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); +import common = require("ui/layouts/wrap-layout/wrap-layout-common"); -function isWidthHeightValid(value: any): boolean { - return isNaN(value) || (value >= 0.0 && value !== Number.POSITIVE_INFINITY); -} +// merge the exports of the common file with the exports of this file +declare var exports; +require("utils/module-merge").merge(common, exports); -function isValidOrientation(value: any): boolean { - return value === enums.Orientation.vertical || value === enums.Orientation.horizontal; -} - -export class WrapLayout extends layouts.Layout implements definition.WrapLayout { +export class WrapLayout extends common.WrapLayout { private _lenghts: Array; - public static orientationProperty = new dependencyObservable.Property( - "orientation", - "WrapLayout", - new proxy.PropertyMetadata(enums.Orientation.horizontal, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - isValidOrientation) - ); - - public static itemWidthProperty = new dependencyObservable.Property( - "itemWidth", - "WrapLayout", - new proxy.PropertyMetadata(Number.NaN, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - isWidthHeightValid) - ); - - public static itemHeightProperty = new dependencyObservable.Property( - "itemHeight", - "WrapLayout", - new proxy.PropertyMetadata(Number.NaN, - dependencyObservable.PropertyMetadataSettings.AffectsLayout, - undefined, - isWidthHeightValid) - ); - - get orientation(): string { - return this._getValue(WrapLayout.orientationProperty); - } - set orientation(value: string) { - this._setValue(WrapLayout.orientationProperty, value); - } - - get itemWidth(): number { - return this._getValue(WrapLayout.itemWidthProperty); - } - set itemWidth(value: number) { - this._setValue(WrapLayout.itemWidthProperty, value); - } - - get itemHeight(): number { - return this._getValue(WrapLayout.itemHeightProperty); - } - set itemHeight(value: number) { - this._setValue(WrapLayout.itemHeightProperty, value); - } - private static getChildMeasureSpec(parentMode: number, parentLength: number, itemLength): number { - if (!isNaN(itemLength)) { + if (itemLength > 0) { return utils.layout.makeMeasureSpec(itemLength, utils.layout.EXACTLY); } else if (parentMode === utils.layout.UNSPECIFIED) { diff --git a/ui/scroll-view/scroll-view.android.ts b/ui/scroll-view/scroll-view.android.ts index 4cf4a887d..33fe51e4c 100644 --- a/ui/scroll-view/scroll-view.android.ts +++ b/ui/scroll-view/scroll-view.android.ts @@ -1,5 +1,4 @@ import dependencyObservable = require("ui/core/dependency-observable"); -import view = require("ui/core/view"); import definition = require("ui/scroll-view"); import contentView = require("ui/content-view"); import common = require("ui/scroll-view/scroll-view-common"); @@ -8,97 +7,12 @@ import enums = require("ui/enums"); global.moduleMerge(common, exports); -var OWNER = "_owner"; -var STATE = "_scrollViewState"; - common.orientationProperty.metadata.onValueChanged = function scrollViewOrientationChanged(data: dependencyObservable.PropertyChangeData) { (data.object)._onOrientationChanged(data.oldValue, data.newValue); } -interface ScrollViewState { - scrollX: number; - scrollY: number; -} - -function onMeasureScrollView(widthMeasureSpec: number, heightMeasureSpec: number) { - var owner: view.View = this.owner; - owner.onMeasure(widthMeasureSpec, heightMeasureSpec); - this.setMeasuredDimension(owner.getMeasuredWidth(), owner.getMeasuredHeight()); -} - -function onLayoutScrollView(changed: boolean, left: number, top: number, right: number, bottom: number) { - var owner: view.View = this.owner; - owner.onLayout(left, top, right, bottom); - - // Restore scroll state on the first layout after native instance is recreated. - var state: ScrollViewState = owner[STATE]; - if (state) { - this.scrollTo(state.scrollX, state.scrollY); - delete owner[STATE]; - } -} - -function onSaveInstanceStateNative(): android.os.Parcelable { - var state: ScrollViewState = { - scrollX: this.getScrollX(), - scrollY: this.getScrollY() - } - this.owner[STATE] = state; - return this.super.onSaveInstanceState(); -} - -class NativeVerticalScrollView extends android.widget.ScrollView { - constructor(ctx) { - super(ctx); - return global.__native(this); - } - - get owner() { - return this[OWNER]; - } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number) { - onMeasureScrollView.apply(this, [widthMeasureSpec, heightMeasureSpec]); - } - - public onLayout(changed: boolean, left: number, top: number, right: number, bottom: number) { - onLayoutScrollView.apply(this, [changed, left, top, right, bottom]); - } - - public onSaveInstanceState() { - onSaveInstanceStateNative.apply(this, []); - } -}; - -class NativeHorizontalScrollView extends android.widget.HorizontalScrollView { - constructor(ctx) { - super(ctx); - return global.__native(this); - } - - get owner() { - return this[OWNER]; - } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number) { - onMeasureScrollView.apply(this, [widthMeasureSpec, heightMeasureSpec]); - } - - public onLayout(changed: boolean, left: number, top: number, right: number, bottom: number) { - onLayoutScrollView.apply(this, [changed, left, top, right, bottom]); - } - - public onSaveInstanceState() { - onSaveInstanceStateNative.apply(this, []); - } -}; - export class ScrollView extends contentView.ContentView implements definition.ScrollView { - private _android: android.widget.FrameLayout; - private _contentMeasuredWidth: number = 0; - private _contentMeasuredHeight: number = 0; - - private _scrollableLength: number = 0; + private _android: org.nativescript.widgets.VerticalScrollView | org.nativescript.widgets.HorizontalScrollView; private _androidViewId: number; get android(): android.view.ViewGroup { @@ -137,7 +51,7 @@ export class ScrollView extends contentView.ContentView implements definition.Sc return 0; } - return this._scrollableLength; + return this._android.getScrollableLength(); } get scrollableHeight(): number { @@ -145,19 +59,18 @@ export class ScrollView extends contentView.ContentView implements definition.Sc return 0; } - return this._scrollableLength; + return this._android.getScrollableLength(); } public scrollToVerticalOffset(value: number, animated: boolean) { if (this._android && this.orientation === enums.Orientation.vertical) { value *= utils.layout.getDisplayDensity(); - var scrollView: android.widget.ScrollView = (this._android); if (animated) { - scrollView.smoothScrollTo(0, value); + this._android.smoothScrollTo(0, value); } else { - scrollView.scrollTo(0, value); + this._android.scrollTo(0, value); } } } @@ -166,30 +79,28 @@ export class ScrollView extends contentView.ContentView implements definition.Sc if (this._android && this.orientation === enums.Orientation.horizontal) { value *= utils.layout.getDisplayDensity(); - var scrollView: android.widget.HorizontalScrollView = (this._android); if (animated) { - scrollView.smoothScrollTo(value, 0); + this._android.smoothScrollTo(value, 0); } else { - scrollView.scrollTo(value, 0); + this._android.scrollTo(value, 0); } } } public _createUI() { if (this.orientation === enums.Orientation.horizontal) { - this._android = new NativeHorizontalScrollView(this._context); + this._android = new org.nativescript.widgets.HorizontalScrollView(this._context); } else { - this._android = new NativeVerticalScrollView(this._context); + this._android = new org.nativescript.widgets.VerticalScrollView(this._context); } if (!this._androidViewId) { this._androidViewId = android.view.View.generateViewId(); } - this._android.setId(this._androidViewId); - this._android[OWNER] = this; + this._android.setId(this._androidViewId); } public _onOrientationChanged(oldValue: string, newValue: string) { @@ -205,56 +116,4 @@ export class ScrollView extends contentView.ContentView implements definition.Sc } } } - - public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { - // Don't call measure because it will measure content twice. - var width = utils.layout.getMeasureSpecSize(widthMeasureSpec); - var widthMode = utils.layout.getMeasureSpecMode(widthMeasureSpec); - - var height = utils.layout.getMeasureSpecSize(heightMeasureSpec); - var heightMode = utils.layout.getMeasureSpecMode(heightMeasureSpec); - - var density = utils.layout.getDisplayDensity(); - var child = this.content - if (!child) { - this._scrollableLength = 0; - this._contentMeasuredWidth = this.minWidth * density; - this._contentMeasuredHeight = this.minHeight * density; - } - else { - var childSize: { measuredWidth: number; measuredHeight: number }; - if (this.orientation === enums.Orientation.vertical) { - childSize = view.View.measureChild(this, child, widthMeasureSpec, utils.layout.makeMeasureSpec(0, utils.layout.UNSPECIFIED)); - } - else { - childSize = view.View.measureChild(this, child, utils.layout.makeMeasureSpec(0, utils.layout.UNSPECIFIED), heightMeasureSpec); - } - - this._contentMeasuredWidth = Math.max(childSize.measuredWidth, this.minWidth * density); - this._contentMeasuredHeight = Math.max(childSize.measuredHeight, this.minHeight * density); - } - - var widthAndState = view.View.resolveSizeAndState(this._contentMeasuredWidth, width, widthMode, 0); - var heightAndState = view.View.resolveSizeAndState(this._contentMeasuredHeight, height, heightMode, 0); - - this.setMeasuredDimension(widthAndState, heightAndState); - } - - public onLayout(left: number, top: number, right: number, bottom: number): void { - - var width = (right - left); - var height = (bottom - top); - - if (this.orientation === enums.Orientation.horizontal) { - this._scrollableLength = this._contentMeasuredWidth - width; - view.View.layoutChild(this, this.content, 0, 0, Math.max(this._contentMeasuredWidth, width), height); - } - else { - this._scrollableLength = this._contentMeasuredHeight - height; - view.View.layoutChild(this, this.content, 0, 0, width, Math.max(this._contentMeasuredHeight, height)); - } - - this._scrollableLength /= utils.layout.getDisplayDensity(); - this._scrollableLength = Math.max(0, this._scrollableLength); - } } \ No newline at end of file diff --git a/ui/styling/style.ts b/ui/styling/style.ts index 6b5b08375..baf5cd679 100644 --- a/ui/styling/style.ts +++ b/ui/styling/style.ts @@ -23,6 +23,216 @@ var _handlersCache = {}; // classes like Frame that does not need to handle styling properties. var noStylingClasses = {}; +// on Android we explicitly set propertySettings to None because android will invalidate its layout (skip unnecessary native call). +var AffectsLayout = global.android ? dependencyObservable.PropertyMetadataSettings.None : dependencyObservable.PropertyMetadataSettings.AffectsLayout; + +export interface Thickness { + left: number; + top: number; + right: number; + bottom: number; +} + +export interface CommonLayoutParams { + width: number; + height: number; + + leftMargin: number; + topMargin: number; + rightMargin: number; + bottomMargin: number; + + horizontalAlignment: string; + verticalAlignment: string; +} + +function parseThickness(value: any): Thickness { + var result: Thickness = { 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 layoutParamsComparer(x: CommonLayoutParams, y: CommonLayoutParams): boolean { + return x.width === y.width + && x.height === y.height + && x.leftMargin === y.leftMargin + && x.topMargin === y.topMargin + && x.rightMargin === y.rightMargin + && x.bottomMargin === y.bottomMargin + && x.horizontalAlignment === y.horizontalAlignment + && x.verticalAlignment === y.verticalAlignment +} + +function onLayoutParamsChanged(data: observable.PropertyChangeData) { + var style =