From eac95156d1bc060d9baeed4cfaaaf89f69e913a1 Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 21 Jan 2016 10:56:04 +0200 Subject: [PATCH] Proxy view container --- CrossPlatformModules.csproj | 5 + tsconfig.json | 2 + ui/core/view-common.ts | 27 ++++- ui/core/view.d.ts | 10 +- ui/core/view.ios.ts | 43 ++++--- .../absolute-layout/absolute-layout.ios.ts | 18 +-- ui/layouts/dock-layout/dock-layout.ios.ts | 37 ++---- ui/layouts/grid-layout/grid-layout.ios.ts | 9 +- ui/layouts/layout-base.d.ts | 6 + ui/layouts/layout-base.ts | 64 +++++++--- ui/layouts/stack-layout/stack-layout.ios.ts | 26 +--- ui/layouts/wrap-layout/wrap-layout.ios.ts | 18 +-- ui/view-container/package.json | 2 + ui/view-container/proxy-view-container.d.ts | 6 + ui/view-container/proxy-view-container.ts | 111 ++++++++++++++++++ 15 files changed, 265 insertions(+), 119 deletions(-) create mode 100644 ui/view-container/package.json create mode 100644 ui/view-container/proxy-view-container.d.ts create mode 100644 ui/view-container/proxy-view-container.ts diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj index 0c7ef0322..a4157466d 100644 --- a/CrossPlatformModules.csproj +++ b/CrossPlatformModules.csproj @@ -726,6 +726,8 @@ + + @@ -2087,6 +2089,9 @@ + + PreserveNewest + diff --git a/tsconfig.json b/tsconfig.json index d7bf4287a..e4cc8567a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -664,6 +664,8 @@ "ui/ui.ts", "ui/utils.d.ts", "ui/utils.ios.ts", + "ui/view-container/proxy-view-container.d.ts", + "ui/view-container/proxy-view-container.ts", "ui/web-view/web-view-common.ts", "ui/web-view/web-view.android.ts", "ui/web-view/web-view.d.ts", diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts index e8701b9f3..cb6e4e4fd 100644 --- a/ui/core/view-common.ts +++ b/ui/core/view-common.ts @@ -22,7 +22,7 @@ registerSpecialProperty("class", (instance: definition.View, propertyValue: stri instance.className = propertyValue; }); -function getEventOrGestureName(name: string) : string { +function getEventOrGestureName(name: string): string { return name.indexOf("on") === 0 ? name.substr(2, name.length - 2) : name; } @@ -190,7 +190,7 @@ export class View extends ProxyObject implements definition.View { observe(type: gestures.GestureTypes, callback: (args: gestures.GestureEventData) => void, thisArg?: any): void { if (!this._gestureObservers[type]) { this._gestureObservers[type] = []; - } + } this._gestureObservers[type].push(gestures.observe(this, type, callback, thisArg)); } @@ -926,6 +926,26 @@ export class View extends ProxyObject implements definition.View { // } + _childIndexToNativeChildIndex(index?: number): number { + return index; + } + + _getNativeViewsCount(): number { + return this._isAddedToNativeVisualTree ? 1 : 0; + } + + _eachLayoutView(callback: (View) => void): void { + return callback(this); + } + + _addToSuperview(superview: any, index?: number): boolean { + // IOS specific + return false; + } + _removeFromSuperview(): void { + // IOS specific + } + /** * Core logic for adding a child view to this instance. Used by the framework to handle lifecycle events more centralized. Do not outside the UI Stack implementation. * // TODO: Think whether we need the base Layout routine. @@ -954,7 +974,8 @@ export class View extends ProxyObject implements definition.View { view.style._inheritStyleProperties(); if (!view._isAddedToNativeVisualTree) { - view._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(view, atIndex); + var nativeIndex = this._childIndexToNativeChildIndex(atIndex); + view._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(view, nativeIndex); } // TODO: Discuss this. diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts index a95f66ec4..e10a1dc0f 100644 --- a/ui/core/view.d.ts +++ b/ui/core/view.d.ts @@ -445,7 +445,7 @@ declare module "ui/core/view" { * Sets in-line CSS string as style. * @param style - In-line CSS string. */ - public setInlineStyle(style: string) : void; + public setInlineStyle(style: string): void; public getGestureObservers(type: gestures.GestureTypes): Array; @@ -497,6 +497,14 @@ declare module "ui/core/view" { _removeView(view: View); _context: any /* android.content.Context */; + _childIndexToNativeChildIndex(index?: number): number; + _getNativeViewsCount(): number; + + _eachLayoutView(callback: (View) => void): void; + + _addToSuperview(superview: any, index?: number): boolean; + _removeFromSuperview(); + public _applyXmlAttribute(attribute: string, value: any): boolean; // TODO: Implement logic for stripping these lines out diff --git a/ui/core/view.ios.ts b/ui/core/view.ios.ts index c4725f048..33adf9c67 100644 --- a/ui/core/view.ios.ts +++ b/ui/core/view.ios.ts @@ -265,6 +265,28 @@ export class View extends viewCommon.View { if (this._cachedFrame) { this._setNativeViewFrame(this._nativeView, this._cachedFrame); } + } + + public _addToSuperview(superview: any, atIndex?: number): boolean { + if (superview && this._nativeView) { + var types = require("utils/types"); + + if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.subviews.count) { + superview.addSubview(this._nativeView); + } else { + superview.insertSubviewAtIndex(this._nativeView, atIndex); + } + + return true; + } + + return false; + } + + public _removeFromSuperview() { + if (this._nativeView) { + this._nativeView.removeFromSuperview(); + } } } @@ -290,30 +312,15 @@ export class CustomLayoutView extends View { } public _addViewToNativeVisualTree(child: View, atIndex: number): boolean { - super._addViewToNativeVisualTree(child); + super._addViewToNativeVisualTree(child, atIndex); - if (this._nativeView && child._nativeView) { - var types = require("utils/types"); - - if (types.isNullOrUndefined(atIndex) || atIndex >= this._nativeView.subviews.count) { - this._nativeView.addSubview(child._nativeView); - } - else { - this._nativeView.insertSubviewAtIndex(child._nativeView, atIndex); - } - - return true; - } - - return false; + return child._addToSuperview(this._nativeView, atIndex); } public _removeViewFromNativeVisualTree(child: View): void { super._removeViewFromNativeVisualTree(child); - if (child._nativeView) { - child._nativeView.removeFromSuperview(); - } + child._removeFromSuperview(); } } diff --git a/ui/layouts/absolute-layout/absolute-layout.ios.ts b/ui/layouts/absolute-layout/absolute-layout.ios.ts index d95b29001..2722dbe4f 100644 --- a/ui/layouts/absolute-layout/absolute-layout.ios.ts +++ b/ui/layouts/absolute-layout/absolute-layout.ios.ts @@ -30,16 +30,11 @@ export class AbsoluteLayout extends common.AbsoluteLayout { let childMeasureSpec = utils.layout.makeMeasureSpec(0, utils.layout.UNSPECIFIED); let density = utils.layout.getDisplayDensity(); - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let childSize = View.measureChild(this, child, childMeasureSpec, childMeasureSpec); measureWidth = Math.max(measureWidth, AbsoluteLayout.getLeft(child) * density + childSize.measuredWidth); measureHeight = Math.max(measureHeight, AbsoluteLayout.getTop(child) * density + childSize.measuredHeight); - } + }); measureWidth += (this.paddingLeft + this.paddingRight) * density; measureHeight += (this.paddingTop + this.paddingBottom) * density; @@ -57,12 +52,7 @@ export class AbsoluteLayout extends common.AbsoluteLayout { super.onLayout(left, top, right, bottom); let density = utils.layout.getDisplayDensity(); - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty); let childWidth = child.getMeasuredWidth(); @@ -74,7 +64,7 @@ export class AbsoluteLayout extends common.AbsoluteLayout { let childBottom = childTop + childHeight + (lp.topMargin + lp.bottomMargin) * density; View.layoutChild(this, child, childLeft, childTop, childRight, childBottom); - } + }); AbsoluteLayout.restoreOriginalParams(this); } diff --git a/ui/layouts/dock-layout/dock-layout.ios.ts b/ui/layouts/dock-layout/dock-layout.ios.ts index c5a7dbcad..8843994e9 100644 --- a/ui/layouts/dock-layout/dock-layout.ios.ts +++ b/ui/layouts/dock-layout/dock-layout.ios.ts @@ -35,13 +35,8 @@ export class DockLayout extends common.DockLayout { var childWidthMeasureSpec: number; var childHeightMeasureSpec: number; - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - - if (this.stretchLastChild && i === (count - 1)) { + this.eachLayoutChild((child, last) => { + if (this.stretchLastChild && last) { childWidthMeasureSpec = utils.layout.makeMeasureSpec(remainingWidth, widthMode); childHeightMeasureSpec = utils.layout.makeMeasureSpec(remainingHeight, heightMode); } @@ -72,7 +67,7 @@ export class DockLayout extends common.DockLayout { measureHeight = Math.max(measureHeight, tempHeight + childSize.measuredHeight); break; } - } + }); measureWidth += (this.paddingLeft + this.paddingRight) * density; measureHeight += (this.paddingTop + this.paddingBottom) * density; @@ -100,19 +95,7 @@ export class DockLayout extends common.DockLayout { var remainingWidth = Math.max(0, right - left - ((this.paddingLeft + this.paddingRight) * density)); var remainingHeight = Math.max(0, bottom - top - ((this.paddingTop + this.paddingBottom) * density)); - var count = this.getChildrenCount(); - var childToStretch = null; - if (count > 0 && this.stretchLastChild) { - count--; - childToStretch = this.getChildAt(count); - } - - for (let i = 0; i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty); let childWidth = child.getMeasuredWidth() + (lp.leftMargin + lp.rightMargin) * density; @@ -152,12 +135,12 @@ export class DockLayout extends common.DockLayout { break; } - View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight); - } - - if (childToStretch) { - View.layoutChild(this, childToStretch, x, y, x + remainingWidth, y + remainingHeight); - } + if (!last) { + View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childTop + childHeight); + } else { + View.layoutChild(this, child, x, y, x + remainingWidth, y + remainingHeight); + } + }); DockLayout.restoreOriginalParams(this); } diff --git a/ui/layouts/grid-layout/grid-layout.ios.ts b/ui/layouts/grid-layout/grid-layout.ios.ts index 890de3206..f0bd9c0d4 100644 --- a/ui/layouts/grid-layout/grid-layout.ios.ts +++ b/ui/layouts/grid-layout/grid-layout.ios.ts @@ -145,16 +145,11 @@ export class GridLayout extends common.GridLayout { this.helper.clearMeasureSpecs(); this.helper.init(); - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let measureSpecs = this.map.get(child); this.updateMeasureSpecs(child, measureSpecs); this.helper.addMeasureSpec(measureSpecs); - } + }); this.helper.measure(); diff --git a/ui/layouts/layout-base.d.ts b/ui/layouts/layout-base.d.ts index 950ef00a0..346019148 100644 --- a/ui/layouts/layout-base.d.ts +++ b/ui/layouts/layout-base.d.ts @@ -50,6 +50,12 @@ */ removeChildren(): void; + /** + * Calls the callback for each child that should be laid out. + * @param callback The callback + */ + eachLayoutChild(callback: (child: view.View, isLast: boolean) => void): void; + /** * Iterates over children and changes their width and height to one calculated from percentage values. * diff --git a/ui/layouts/layout-base.ts b/ui/layouts/layout-base.ts index eda56c429..97822bae8 100644 --- a/ui/layouts/layout-base.ts +++ b/ui/layouts/layout-base.ts @@ -1,4 +1,5 @@ import definition = require("ui/layouts/layout-base"); +import types = require("utils/types"); import view = require("ui/core/view"); import dependencyObservable = require("ui/core/dependency-observable"); import proxy = require("ui/core/proxy"); @@ -41,13 +42,13 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo public addChild(child: view.View): void { // TODO: Do we need this method since we have the core logic in the View implementation? - this._addView(child); this._subViews.push(child); + this._addView(child); } public insertChild(child: view.View, atIndex: number): void { - this._addView(child, atIndex); this._subViews.splice(atIndex, 0, child); + this._addView(child, atIndex); } public removeChild(child: view.View): void { @@ -64,19 +65,6 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo } } - public _eachChildView(callback: (child: view.View) => boolean): void { - var i; - var length = this._subViews.length; - var retVal: boolean; - - for (i = 0; i < length; i++) { - retVal = callback(this._subViews[i]); - if (retVal === false) { - break; - } - } - } - get padding(): string { return this.style.padding; } @@ -127,6 +115,52 @@ export class LayoutBase extends view.CustomLayoutView implements definition.Layo } } + public _childIndexToNativeChildIndex(index?: number): number { + if (types.isUndefined(index)) { + return undefined; + } + var result = 0; + for (let i = 0; i < index && i < this._subViews.length; i++) { + result += this._subViews[i]._getNativeViewsCount(); + } + return result; + } + + public _eachChildView(callback: (child: view.View) => boolean): void { + var i; + var length = this._subViews.length; + var retVal: boolean; + + for (i = 0; i < length; i++) { + retVal = callback(this._subViews[i]); + if (retVal === false) { + break; + } + } + } + + public eachLayoutChild(callback: (child: view.View, isLast: boolean) => void): void { + var index = 0; + var lastChild: view.View = null; + + this._eachChildView((cv) => { + cv._eachLayoutView((lv) => { + if (lastChild && lastChild._isVisible) { + callback(lastChild, false); + } + + lastChild = lv; + }); + + return true; + }); + + if (lastChild && lastChild._isVisible) { + callback(lastChild, true); + } + + } + private static onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData): void { var layout = data.object; layout.onClipToBoundsChanged(data.oldValue, data.newValue); diff --git a/ui/layouts/stack-layout/stack-layout.ios.ts b/ui/layouts/stack-layout/stack-layout.ios.ts index 6f3a4d7ac..cc27d003a 100644 --- a/ui/layouts/stack-layout/stack-layout.ios.ts +++ b/ui/layouts/stack-layout/stack-layout.ios.ts @@ -54,12 +54,8 @@ export class StackLayout extends common.StackLayout { } var childSize: { measuredWidth: number; measuredHeight: number }; - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } + this.eachLayoutChild((child, last) => { if (isVertical) { childSize = View.measureChild(this, child, childMeasureSpec, utils.layout.makeMeasureSpec(remainingLength, measureSpec)); measureWidth = Math.max(measureWidth, childSize.measuredWidth); @@ -74,7 +70,7 @@ export class StackLayout extends common.StackLayout { measureWidth += viewWidth; remainingLength = Math.max(0, remainingLength - viewWidth); } - } + }); measureWidth += horizontalPadding; measureHeight += verticalPadding; @@ -129,18 +125,13 @@ export class StackLayout extends common.StackLayout { break; } - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty); let childHeight = child.getMeasuredHeight() + (lp.topMargin + lp.bottomMargin) * density; View.layoutChild(this, child, childLeft, childTop, childRight, childTop + childHeight); childTop += childHeight; - } + }) } private layoutHorizontal(left: number, top: number, right: number, bottom: number): void { @@ -170,17 +161,12 @@ export class StackLayout extends common.StackLayout { break; } - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty); let childWidth = child.getMeasuredWidth() + (lp.leftMargin + lp.rightMargin) * density; View.layoutChild(this, child, childLeft, childTop, childLeft + childWidth, childBottom); childLeft += childWidth; - } + }); } } \ No newline at end of file diff --git a/ui/layouts/wrap-layout/wrap-layout.ios.ts b/ui/layouts/wrap-layout/wrap-layout.ios.ts index 6a0712ffb..01b7e4247 100644 --- a/ui/layouts/wrap-layout/wrap-layout.ios.ts +++ b/ui/layouts/wrap-layout/wrap-layout.ios.ts @@ -56,12 +56,7 @@ export class WrapLayout extends common.WrapLayout { let itemWidth = this.itemWidth; let itemHeight = this.itemHeight; - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { var desiredSize = View.measureChild(this, child, childWidthMeasureSpec, childHeightMeasureSpec); let childMeasuredWidth = useItemWidth ? itemWidth : desiredSize.measuredWidth; let childMeasuredHeight = useItemHeight ? itemHeight : desiredSize.measuredHeight; @@ -99,7 +94,7 @@ export class WrapLayout extends common.WrapLayout { else { this._lengths[rowOrColumn] = Math.max(this._lengths[rowOrColumn], isVertical ? childMeasuredWidth : childMeasuredHeight); } - } + }); if (isVertical) { measureHeight = Math.max(maxLength, measureHeight); @@ -144,12 +139,7 @@ export class WrapLayout extends common.WrapLayout { } var rowOrColumn = 0; - for (let i = 0, count = this.getChildrenCount(); i < count; i++) { - let child = this.getChildAt(i); - if (!child._isVisible) { - continue; - } - + this.eachLayoutChild((child, last) => { // Add margins because layoutChild will sustract them. // * density converts them to device pixels. let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty); @@ -203,7 +193,7 @@ export class WrapLayout extends common.WrapLayout { // Move next child Left position to right. childLeft += childWidth; } - } + }); WrapLayout.restoreOriginalParams(this); } diff --git a/ui/view-container/package.json b/ui/view-container/package.json new file mode 100644 index 000000000..7d943751f --- /dev/null +++ b/ui/view-container/package.json @@ -0,0 +1,2 @@ +{ "name" : "proxy-view-container", + "main" : "proxy-view-container.js" } diff --git a/ui/view-container/proxy-view-container.d.ts b/ui/view-container/proxy-view-container.d.ts new file mode 100644 index 000000000..2495eddd2 --- /dev/null +++ b/ui/view-container/proxy-view-container.d.ts @@ -0,0 +1,6 @@ +declare module "ui/proxy-view-container" { + import layout = require("ui/layouts/layout-base"); + + export class ProxyViewContainer extends layout.LayoutBase { + } +} diff --git a/ui/view-container/proxy-view-container.ts b/ui/view-container/proxy-view-container.ts new file mode 100644 index 000000000..59f444501 --- /dev/null +++ b/ui/view-container/proxy-view-container.ts @@ -0,0 +1,111 @@ +import types = require("utils/types"); +import view = require("ui/core/view"); +import definition = require("ui/proxy-view-container"); +import trace = require("trace"); +import layout = require("ui/layouts/layout-base"); +/** + * Proxy view container that adds all its native children dirctly to the parent. + * To be used as a logical grouping container of views. + */ +// Cases to cover: +// * Child is added to the attached proxy. Handled in _addViewToNativeVisualTree. +// * Proxy (with children) is added to the DOM. +// - IOS: Handled in _addToSuperview - when the proxy is added, it adds all its children to the new parent. +// - Android: _onAttached calls _addViewToNativeVisualTree recoursively when the proxy is added to the parent. +// * Child is removed from attached proxy. Handled in _removeViewFromNativeVisualTree. +// * Proxy (with children) is removed form the DOM. +// - IOS: Handled in _removeFromSuperview - when the proxy is removed, it removes all its children from its parent. +// - Android: _onDetached calls _removeViewFromNativeVisualTree recoursively when the proxy is removed from its parent. +export class ProxyViewContainer extends layout.LayoutBase implements definition.ProxyViewContainer { + // No native view for proxy container. + get ios(): any { + return null; + } + + get android(): any { + return null; + } + + get _nativeView(): any { + return null; + } + + public _createUI() { + // + } + + public _getNativeViewsCount(): number { + let result = 0; + this._eachChildView((cv) => { + result += cv._getNativeViewsCount(); + return true; + }); + + return result; + } + + public _eachLayoutView(callback: (View) => void): void { + this._eachChildView((cv) => { + cv._eachLayoutView(callback); + return true; + }); + } + + public _addViewToNativeVisualTree(child: view.View, atIndex?: number): boolean { + trace.write("ViewContainer._addViewToNativeVisualTree for a child " + view + " ViewContainer.parent: " + this.parent, trace.categories.ViewHierarchy); + super._addViewToNativeVisualTree(child); + + var parent = this.parent; + if (parent) { + let baseIndex = 0; + let insideIndex = 0; + if (parent instanceof layout.LayoutBase) { + baseIndex = parent.getChildIndex(this); + baseIndex = parent._childIndexToNativeChildIndex(baseIndex); + } + + if (types.isDefined(atIndex)) { + insideIndex = this._childIndexToNativeChildIndex(atIndex); + } else { + // Add last; + insideIndex = this._getNativeViewsCount(); + } + + trace.write("ProxyViewContainer._addViewToNativeVisualTree at: " + atIndex + " base: " + baseIndex + " additional: " + insideIndex, trace.categories.ViewHierarchy); + return parent._addViewToNativeVisualTree(child, baseIndex + insideIndex); + } + + return false; + } + + public _removeViewFromNativeVisualTree(child: view.View): void { + trace.write("ProxyViewContainer._removeViewFromNativeVisualTree for a child " + view + " ViewContainer.parent: " + this.parent, trace.categories.ViewHierarchy); + super._removeViewFromNativeVisualTree(child); + + var parent = this.parent; + if (parent) { + return parent._removeViewFromNativeVisualTree(child); + } + } + + public _addToSuperview(superview: any, atIndex?: number): boolean { + var index = 0; + this._eachChildView((cv) => { + if (!cv._isAddedToNativeVisualTree) { + cv._isAddedToNativeVisualTree = this._addViewToNativeVisualTree(cv, index++); + } + return true; + }); + + return true; + } + + public _removeFromSuperview() { + this._eachChildView((cv) => { + if (cv._isAddedToNativeVisualTree) { + this._removeViewFromNativeVisualTree(cv); + } + return true; + }); + } +}