// Definitions. import { Point, CustomLayoutView as CustomLayoutViewDefinition, dip } from "."; import { GestureTypes, GestureEventData } from "../../gestures"; // Types. import { Background, ad as androidBackground } from "../../styling/background"; import { ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty, traceEnabled, traceWrite, traceCategories, traceNotifyEvent } from "./view-common"; import { Length, PercentLength, Visibility, HorizontalAlignment, VerticalAlignment, visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty, minWidthProperty, minHeightProperty, widthProperty, heightProperty, marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty, rotateProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty, zIndexProperty, backgroundInternalProperty } from "../../styling/style-properties"; export * from "./view-common"; interface TouchListener { new (owner: View): android.view.View.OnTouchListener; } let TouchListener: TouchListener; let disableUserInteractionListener: org.nativescript.widgets.DisableUserInteractionListener; function initializeDisabledListener(): void { if (disableUserInteractionListener) { return; } disableUserInteractionListener = new org.nativescript.widgets.DisableUserInteractionListener(); } function initializeTouchListener(): void { if (TouchListener) { return; } @Interfaces([android.view.View.OnTouchListener]) class TouchListenerImpl extends java.lang.Object implements android.view.View.OnTouchListener { constructor(private owner: View) { super(); return global.__native(this); } onTouch(view: android.view.View, event: android.view.MotionEvent): boolean { const owner = this.owner; for (let type in owner._gestureObservers) { let list = owner._gestureObservers[type]; list.forEach(element => { element.androidOnTouchEvent(event); }); } let nativeView = owner.nativeView; if (!nativeView || !nativeView.onTouchEvent) { return false; } return nativeView.onTouchEvent(event); } } TouchListener = TouchListenerImpl; } export class View extends ViewCommon { private touchListenerIsSet: boolean; private touchListener: android.view.View.OnTouchListener; nativeView: android.view.View; // TODO: Implement unobserve that detach the touchListener. observe(type: GestureTypes, callback: (args: GestureEventData) => void, thisArg?: any): void { super.observe(type, callback, thisArg); if (this.isLoaded && !this.touchListenerIsSet) { this.setOnTouchListener(); } } public onLoaded() { super.onLoaded(); this.setOnTouchListener(); } public onUnloaded() { if (this.touchListenerIsSet) { this.nativeView.setOnTouchListener(null); this.touchListenerIsSet = false; } this._cancelAllAnimations(); super.onUnloaded(); } private hasGestureObservers() { return this._gestureObservers && Object.keys(this._gestureObservers).length > 0 } private setOnTouchListener() { if (this.nativeView && this.hasGestureObservers()) { this.touchListenerIsSet = true; if (this.nativeView.setClickable) { this.nativeView.setClickable(true); } initializeTouchListener(); this.touchListener = this.touchListener || new TouchListener(this); this.nativeView.setOnTouchListener(this.touchListener); } } get isLayoutRequired(): boolean { return !this.isLayoutValid; } 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); } } public requestLayout(): void { super.requestLayout(); if (this.nativeView) { return this.nativeView.requestLayout(); } } public measure(widthMeasureSpec: number, heightMeasureSpec: number): void { super.measure(widthMeasureSpec, heightMeasureSpec); this.onMeasure(widthMeasureSpec, heightMeasureSpec); } public layout(left: number, top: number, right: number, bottom: number): void { super.layout(left, top, right, bottom); this.onLayout(left, top, right, bottom); } public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { let view = this.nativeView; if (view) { view.measure(widthMeasureSpec, heightMeasureSpec); this.setMeasuredDimension(view.getMeasuredWidth(), view.getMeasuredHeight()); } } public onLayout(left: number, top: number, right: number, bottom: number): void { let view = this.nativeView; if (view) { this.layoutNativeView(left, top, right, bottom); } } _getCurrentLayoutBounds(): { left: number; top: number; right: number; bottom: number } { if (this.nativeView && !this.isCollapsed) { return { left: this.nativeView.getLeft(), top: this.nativeView.getTop(), right: this.nativeView.getRight(), bottom: this.nativeView.getBottom() }; } else { return { left: 0, top: 0, right: 0, bottom: 0 }; } } public getMeasuredWidth(): number { if (this.nativeView) { return this.nativeView.getMeasuredWidth(); } return super.getMeasuredWidth(); } public getMeasuredHeight(): number { if (this.nativeView) { return this.nativeView.getMeasuredHeight(); } return super.getMeasuredHeight(); } public focus(): boolean { if (this.nativeView) { return this.nativeView.requestFocus(); } return false; } public getLocationInWindow(): Point { if (!this.nativeView || !this.nativeView.getWindowToken()) { return undefined; } let nativeArray = (Array).create("int", 2); this.nativeView.getLocationInWindow(nativeArray); return { x: layout.toDeviceIndependentPixels(nativeArray[0]), y: layout.toDeviceIndependentPixels(nativeArray[1]), } } public getLocationOnScreen(): Point { if (!this.nativeView || !this.nativeView.getWindowToken()) { return undefined; } let nativeArray = (Array).create("int", 2); this.nativeView.getLocationOnScreen(nativeArray); return { x: layout.toDeviceIndependentPixels(nativeArray[0]), y: layout.toDeviceIndependentPixels(nativeArray[1]), } } public getLocationRelativeTo(otherView: ViewCommon): Point { if (!this.nativeView || !this.nativeView.getWindowToken() || !otherView.nativeView || !otherView.nativeView.getWindowToken() || this.nativeView.getWindowToken() !== otherView.nativeView.getWindowToken()) { return undefined; } let myArray = (Array).create("int", 2); this.nativeView.getLocationOnScreen(myArray); let otherArray = (Array).create("int", 2); otherView.nativeView.getLocationOnScreen(otherArray); return { x: layout.toDeviceIndependentPixels(myArray[0] - otherArray[0]), y: layout.toDeviceIndependentPixels(myArray[1] - otherArray[1]), } } public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number { let result = size; switch (specMode) { case layout.UNSPECIFIED: result = size; break; case layout.AT_MOST: if (specSize < size) { result = specSize | layout.MEASURED_STATE_TOO_SMALL; } break; case layout.EXACTLY: result = specSize; break; } return result | (childMeasuredState & layout.MEASURED_STATE_MASK); } [isEnabledProperty.getDefault](): boolean { return this.nativeView.isEnabled(); } [isEnabledProperty.setNative](value: boolean) { this.nativeView.setEnabled(value); } [originXProperty.getDefault](): number { return this.nativeView.getPivotX(); } [originXProperty.setNative](value: number) { org.nativescript.widgets.OriginPoint.setX(this.nativeView, value); } [originYProperty.getDefault](): number { return this.nativeView.getPivotY(); } [originYProperty.setNative](value: number) { org.nativescript.widgets.OriginPoint.setY(this.nativeView, value); } [automationTextProperty.getDefault](): string { return this.nativeView.getContentDescription(); } [automationTextProperty.setNative](value: string) { this.nativeView.setContentDescription(value); } [isUserInteractionEnabledProperty.getDefault](): boolean { return true; } [isUserInteractionEnabledProperty.setNative](value: boolean) { if (!value) { initializeDisabledListener(); // User interaction is disabled -- we stop it and we do not care whether someone wants to listen for gestures. this.nativeView.setOnTouchListener(disableUserInteractionListener); } else { this.setOnTouchListener(); } } [visibilityProperty.getDefault](): Visibility { let nativeVisibility = this.nativeView.getVisibility(); switch (nativeVisibility) { case android.view.View.VISIBLE: return "visible"; case android.view.View.INVISIBLE: return "hidden"; case android.view.View.GONE: return "collapse"; default: throw new Error(`Unsupported android.view.View visibility: ${nativeVisibility}. Currently supported values are android.view.View.VISIBLE, android.view.View.INVISIBLE, android.view.View.GONE.`); } } [visibilityProperty.setNative](value: Visibility) { switch (value) { case "visible": this.nativeView.setVisibility(android.view.View.VISIBLE); break; case "hidden": this.nativeView.setVisibility(android.view.View.INVISIBLE); break; case "collapse": this.nativeView.setVisibility(android.view.View.GONE); break; default: throw new Error(`Invalid visibility value: ${value}. Valid values are: visible, hidden, collapse.`); } } [opacityProperty.getDefault](): number { return this.nativeView.getAlpha(); } [opacityProperty.setNative](value: number) { this.nativeView.setAlpha(float(value)); } [horizontalAlignmentProperty.getDefault](): HorizontalAlignment { return org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeView); } [horizontalAlignmentProperty.setNative](value: HorizontalAlignment) { const nativeView = this.nativeView; const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams(); // Set only if params gravity exists. if (lp.gravity !== undefined) { switch (value) { case "left": lp.gravity = android.view.Gravity.LEFT | (lp.gravity & android.view.Gravity.VERTICAL_GRAVITY_MASK); break; case "center": lp.gravity = android.view.Gravity.CENTER_HORIZONTAL | (lp.gravity & android.view.Gravity.VERTICAL_GRAVITY_MASK); break; case "right": lp.gravity = android.view.Gravity.RIGHT | (lp.gravity & android.view.Gravity.VERTICAL_GRAVITY_MASK); break; case "stretch": lp.gravity = android.view.Gravity.FILL_HORIZONTAL | (lp.gravity & android.view.Gravity.VERTICAL_GRAVITY_MASK); break; } nativeView.setLayoutParams(lp); } } [verticalAlignmentProperty.getDefault](): VerticalAlignment { return org.nativescript.widgets.ViewHelper.getVerticalAlignment(this.nativeView); } [verticalAlignmentProperty.setNative](value: VerticalAlignment) { const nativeView = this.nativeView; const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams(); // Set only if params gravity exists. if (lp.gravity !== undefined) { switch (value) { case "top": lp.gravity = android.view.Gravity.TOP | (lp.gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK); break; case "middle": lp.gravity = android.view.Gravity.CENTER_VERTICAL | (lp.gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK); break; case "bottom": lp.gravity = android.view.Gravity.BOTTOM | (lp.gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK); break; case "stretch": lp.gravity = android.view.Gravity.FILL_VERTICAL | (lp.gravity & android.view.Gravity.HORIZONTAL_GRAVITY_MASK); break; } nativeView.setLayoutParams(lp); } } [rotateProperty.getDefault](): number { return org.nativescript.widgets.ViewHelper.getRotate(this.nativeView); } [rotateProperty.setNative](value: number) { org.nativescript.widgets.ViewHelper.setRotate(this.nativeView, float(value)); } [scaleXProperty.getDefault](): number { return org.nativescript.widgets.ViewHelper.getScaleX(this.nativeView); } [scaleXProperty.setNative](value: number) { org.nativescript.widgets.ViewHelper.setScaleX(this.nativeView, float(value)); } [scaleYProperty.getDefault](): number { return org.nativescript.widgets.ViewHelper.getScaleY(this.nativeView); } [scaleYProperty.setNative](value: number) { org.nativescript.widgets.ViewHelper.setScaleY(this.nativeView, float(value)); } [translateXProperty.getDefault](): dip { return layout.toDeviceIndependentPixels(org.nativescript.widgets.ViewHelper.getTranslateX(this.nativeView)); } [translateXProperty.setNative](value: dip) { org.nativescript.widgets.ViewHelper.setTranslateX(this.nativeView, layout.toDevicePixels(value)); } [translateYProperty.getDefault](): dip { return layout.toDeviceIndependentPixels(org.nativescript.widgets.ViewHelper.getTranslateY(this.nativeView)); } [translateYProperty.setNative](value: dip) { org.nativescript.widgets.ViewHelper.setTranslateY(this.nativeView, layout.toDevicePixels(value)); } [zIndexProperty.getDefault](): number { return org.nativescript.widgets.ViewHelper.getZIndex(this.nativeView); } [zIndexProperty.setNative](value: number) { org.nativescript.widgets.ViewHelper.setZIndex(this.nativeView, value); // let nativeView = this.nativeView; // if (nativeView instanceof android.widget.Button) { // nativeView.setStateListAnimator(null); // } } [backgroundInternalProperty.getDefault](): android.graphics.drawable.Drawable { return this.nativeView.getBackground(); } [backgroundInternalProperty.setNative](value: android.graphics.drawable.Drawable | Background) { if (value instanceof android.graphics.drawable.Drawable) { this.nativeView.setBackground(value); } else { androidBackground.onBackgroundOrBorderPropertyChanged(this); } } [minWidthProperty.setNative](value: Length) { if (this.parent instanceof CustomLayoutView && this.parent.nativeView) { this.parent._setChildMinWidthNative(this); } else { this._setMinWidthNative(this.minWidth); } } [minHeightProperty.setNative](value: Length) { if (this.parent instanceof CustomLayoutView && this.parent.nativeView) { this.parent._setChildMinHeightNative(this); } else { this._setMinHeightNative(this.minHeight); } } } export class CustomLayoutView extends View implements CustomLayoutViewDefinition { nativeView: android.view.ViewGroup; public createNativeView() { return new org.nativescript.widgets.ContentLayout(this._context); } public _addViewToNativeVisualTree(child: ViewCommon, atIndex: number = -1): boolean { super._addViewToNativeVisualTree(child); if (this.nativeView && child.nativeView) { if (traceEnabled()) { traceWrite(`${this}.nativeView.addView(${child}.nativeView, ${atIndex})`, traceCategories.VisualTreeEvents); } this.nativeView.addView(child.nativeView, atIndex); if (child instanceof View) { this._updateNativeLayoutParams(child); } return true; } return false; } public _updateNativeLayoutParams(child: View): void { this._setChildMinWidthNative(child); this._setChildMinHeightNative(child); } public _setChildMinWidthNative(child: View): void { child._setMinWidthNative(child.minWidth); } public _setChildMinHeightNative(child: View): void { child._setMinHeightNative(child.minHeight); } public _removeViewFromNativeVisualTree(child: ViewCommon): void { super._removeViewFromNativeVisualTree(child); if (this.nativeView && child.nativeView) { this.nativeView.removeView(child.nativeView); if (traceEnabled()) { traceWrite(`${this}.nativeView.removeView(${child}.nativeView)`, traceCategories.VisualTreeEvents); traceNotifyEvent(child, "childInLayoutRemovedFromNativeVisualTree"); } } } } type NativeSetter = { (view: android.view.View, value: number): void }; type NativeGetter = { (view: android.view.View): number }; const percentNotSupported = (view: android.view.View, value: number) => { throw new Error("PercentLength is not supported."); }; interface NativePercentLengthPropertyOptions { getter?: string | symbol; setter: string | symbol; auto?: number; getPixels?: NativeGetter; setPixels: NativeSetter; setPercent?: NativeSetter } function createNativePercentLengthProperty(options: NativePercentLengthPropertyOptions) { const { getter, setter, auto = 0 } = options; let setPixels, getPixels, setPercent; if (getter) { View.prototype[getter] = function(this: View): PercentLength { if (options) { setPixels = options.setPixels; getPixels = options.getPixels; setPercent = options.setPercent || percentNotSupported; options = null; } const value = getPixels(this.nativeView); if (value == auto) { // tslint:disable-line return "auto"; } else { return { value, unit: "px" }; } } } if (setter) { View.prototype[setter] = function(this: View, length: PercentLength) { if (options) { setPixels = options.setPixels; getPixels = options.getPixels; setPercent = options.setPercent || percentNotSupported; options = null; } if (length == "auto") { // tslint:disable-line setPixels(this.nativeView, auto); } else if (typeof length === "number") { setPixels(this.nativeView, layout.round(layout.toDevicePixels(length))); } else if (length.unit == "dip") { // tslint:disable-line setPixels(this.nativeView, layout.round(layout.toDevicePixels(length.value))); } else if (length.unit == "px") { // tslint:disable-line setPixels(this.nativeView, layout.round(length.value)); } else if (length.unit == "%") { // tslint:disable-line setPercent(this.nativeView, length.value); } else { throw new Error(`Unsupported PercentLength ${length}`); } } } } createNativePercentLengthProperty({ getter: marginTopProperty.getDefault, setter: marginTopProperty.setNative, get getPixels() { return org.nativescript.widgets.ViewHelper.getMarginTop }, get setPixels() { return org.nativescript.widgets.ViewHelper.setMarginTop }, get setPercent() { return org.nativescript.widgets.ViewHelper.setMarginTopPercent } }); createNativePercentLengthProperty({ getter: marginRightProperty.getDefault, setter: marginRightProperty.setNative, get getPixels() { return org.nativescript.widgets.ViewHelper.getMarginRight }, get setPixels() { return org.nativescript.widgets.ViewHelper.setMarginRight }, get setPercent() { return org.nativescript.widgets.ViewHelper.setMarginRightPercent } }); createNativePercentLengthProperty({ getter: marginBottomProperty.getDefault, setter: marginBottomProperty.setNative, get getPixels() { return org.nativescript.widgets.ViewHelper.getMarginBottom }, get setPixels() { return org.nativescript.widgets.ViewHelper.setMarginBottom }, get setPercent() { return org.nativescript.widgets.ViewHelper.setMarginBottomPercent } }); createNativePercentLengthProperty({ getter: marginLeftProperty.getDefault, setter: marginLeftProperty.setNative, get getPixels() { return org.nativescript.widgets.ViewHelper.getMarginLeft }, get setPixels() { return org.nativescript.widgets.ViewHelper.setMarginLeft }, get setPercent() { return org.nativescript.widgets.ViewHelper.setMarginLeftPercent } }); createNativePercentLengthProperty({ getter: widthProperty.getDefault, setter: widthProperty.setNative, auto: -1, //android.view.ViewGroup.LayoutParams.MATCH_PARENT, get getPixels() { return org.nativescript.widgets.ViewHelper.getWidth }, get setPixels() { return org.nativescript.widgets.ViewHelper.setWidth }, get setPercent() { return org.nativescript.widgets.ViewHelper.setWidthPercent } }); createNativePercentLengthProperty({ getter: heightProperty.getDefault, setter: heightProperty.setNative, auto: -1, //android.view.ViewGroup.LayoutParams.MATCH_PARENT, get getPixels() { return org.nativescript.widgets.ViewHelper.getHeight }, get setPixels() { return org.nativescript.widgets.ViewHelper.setHeight }, get setPercent() { return org.nativescript.widgets.ViewHelper.setHeightPercent } }); createNativePercentLengthProperty({ setter: "_setMinWidthNative", get setPixels() { return org.nativescript.widgets.ViewHelper.setMinWidth } }); createNativePercentLengthProperty({ setter: "_setMinHeightNative", get setPixels() { return org.nativescript.widgets.ViewHelper.setMinHeight } });