From 262568314b17241192c9ca802ab3dc6e160a9acb Mon Sep 17 00:00:00 2001 From: Manol Donev Date: Tue, 18 Sep 2018 18:59:40 +0300 Subject: [PATCH] feat: add ability to pass touch event thru parent view (#6204) * feat: enhance hit-testing support * refactor(android): update passthroughParent logic as per reqs * refactor: move isPassthroughParentEnabled to LayoutBase * Update view-common.ts * refactor: touchListener logic * refactor: renames * added ui test page --- apps/app/ui-tests-app/layouts/main-page.ts | 1 + .../ui-tests-app/layouts/passThroughParent.ts | 19 +++++++++++ .../layouts/passThroughParent.xml | 24 ++++++++++++++ tns-core-modules/ui/core/view/view-common.ts | 2 +- tns-core-modules/ui/core/view/view.android.ts | 32 +++++++++---------- tns-core-modules/ui/core/view/view.ios.ts | 2 +- .../ui/layouts/layout-base-common.ts | 4 +++ .../ui/layouts/layout-base.android.ts | 6 +++- tns-core-modules/ui/layouts/layout-base.d.ts | 8 +++++ .../ui/layouts/layout-base.ios.ts | 8 ++++- 10 files changed, 85 insertions(+), 21 deletions(-) create mode 100644 apps/app/ui-tests-app/layouts/passThroughParent.ts create mode 100644 apps/app/ui-tests-app/layouts/passThroughParent.xml diff --git a/apps/app/ui-tests-app/layouts/main-page.ts b/apps/app/ui-tests-app/layouts/main-page.ts index 3111f135b..5d0b7adf2 100644 --- a/apps/app/ui-tests-app/layouts/main-page.ts +++ b/apps/app/ui-tests-app/layouts/main-page.ts @@ -21,6 +21,7 @@ export function loadExamples() { examples.set("pgrid", "layouts-percent/grid"); examples.set("pstack", "layouts-percent/stack"); examples.set("pwrap", "layouts-percent/wrap"); + examples.set("passThroughParent", "layouts/passThroughParent"); examples.set("stacklayout-6059", "layouts/stacklayout-6059"); return examples; diff --git a/apps/app/ui-tests-app/layouts/passThroughParent.ts b/apps/app/ui-tests-app/layouts/passThroughParent.ts new file mode 100644 index 000000000..1080e0b3c --- /dev/null +++ b/apps/app/ui-tests-app/layouts/passThroughParent.ts @@ -0,0 +1,19 @@ +export function onOuterWrapLayoutTap() { + console.log("on outer wrap layout tap"); +} + +export function onStackLayoutThrowTap() { + throw new Error("Should not tap layout with IsPassThroughParentEnabled=true"); +} + +export function onUserInteractionDisabledTap() { + throw new Error("Should not tap button with IsUserInteractionEnabled=false"); +} + +export function onDisabledThrowTap() { + throw new Error("Should not tap button with IsEnabled=false"); +} + +export function onTap() { + console.log("on button tap"); +} \ No newline at end of file diff --git a/apps/app/ui-tests-app/layouts/passThroughParent.xml b/apps/app/ui-tests-app/layouts/passThroughParent.xml new file mode 100644 index 000000000..d6795f742 --- /dev/null +++ b/apps/app/ui-tests-app/layouts/passThroughParent.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/tns-core-modules/ui/core/view/view-common.ts b/tns-core-modules/ui/core/view/view-common.ts index 0f9cb7c42..2ceb7cf9a 100644 --- a/tns-core-modules/ui/core/view/view-common.ts +++ b/tns-core-modules/ui/core/view/view-common.ts @@ -1021,4 +1021,4 @@ export const isEnabledProperty = new Property({ isEnabledProperty.register(ViewCommon); export const isUserInteractionEnabledProperty = new Property({ name: "isUserInteractionEnabled", defaultValue: true, valueConverter: booleanConverter }); -isUserInteractionEnabledProperty.register(ViewCommon); \ No newline at end of file +isUserInteractionEnabledProperty.register(ViewCommon); diff --git a/tns-core-modules/ui/core/view/view.android.ts b/tns-core-modules/ui/core/view/view.android.ts index 63398ea40..54ddc9a05 100644 --- a/tns-core-modules/ui/core/view/view.android.ts +++ b/tns-core-modules/ui/core/view/view.android.ts @@ -357,15 +357,20 @@ export class View extends ViewCommon { } private setOnTouchListener() { - if (this.nativeViewProtected && this.hasGestureObservers()) { - this.touchListenerIsSet = true; - if (this.nativeViewProtected.setClickable) { - this.nativeViewProtected.setClickable(true); - } + if (!this.nativeViewProtected || !this.hasGestureObservers()) { + return; + } + + // do not set noop listener that handles the event (disabled listener) if IsUserInteractionEnabled is + // false as we might need the ability for the event to pass through to a parent view + initializeTouchListener(); + this.touchListener = this.touchListener || new TouchListener(this); + this.nativeViewProtected.setOnTouchListener(this.touchListener); - initializeTouchListener(); - this.touchListener = this.touchListener || new TouchListener(this); - this.nativeViewProtected.setOnTouchListener(this.touchListener); + this.touchListenerIsSet = true; + + if (this.nativeViewProtected.setClickable) { + this.nativeViewProtected.setClickable(this.isUserInteractionEnabled); } } @@ -605,15 +610,8 @@ export class View extends ViewCommon { } [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.nativeViewProtected.setOnTouchListener(disableUserInteractionListener); - } else { - this.setOnTouchListener(); - if (!this.touchListenerIsSet) { - this.nativeViewProtected.setOnTouchListener(null); - } + if (this.nativeViewProtected.setClickable) { + this.nativeViewProtected.setClickable(value); } } diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index a50543d97..a1a68d7b9 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -1,4 +1,4 @@ -// Definitions. +// Definitions. import { Point, View as ViewDefinition, dip } from "."; import { ViewBase } from "../view-base"; diff --git a/tns-core-modules/ui/layouts/layout-base-common.ts b/tns-core-modules/ui/layouts/layout-base-common.ts index 167d0a363..104338337 100644 --- a/tns-core-modules/ui/layouts/layout-base-common.ts +++ b/tns-core-modules/ui/layouts/layout-base-common.ts @@ -106,6 +106,7 @@ export class LayoutBaseCommon extends CustomLayoutView implements LayoutBaseDefi } public clipToBounds: boolean; + public isPassThroughParentEnabled: boolean; public _childIndexToNativeChildIndex(index?: number): number { if (index === undefined) { @@ -151,3 +152,6 @@ export class LayoutBaseCommon extends CustomLayoutView implements LayoutBaseDefi export const clipToBoundsProperty = new Property({ name: "clipToBounds", defaultValue: true, valueConverter: booleanConverter }); clipToBoundsProperty.register(LayoutBaseCommon); + +export const isPassThroughParentEnabledProperty = new Property({ name: "isPassThroughParentEnabled", defaultValue: false, valueConverter: booleanConverter }); +isPassThroughParentEnabledProperty.register(LayoutBaseCommon); diff --git a/tns-core-modules/ui/layouts/layout-base.android.ts b/tns-core-modules/ui/layouts/layout-base.android.ts index 2f0eb60dd..17b70fde4 100644 --- a/tns-core-modules/ui/layouts/layout-base.android.ts +++ b/tns-core-modules/ui/layouts/layout-base.android.ts @@ -1,5 +1,5 @@ import { - LayoutBaseCommon, clipToBoundsProperty, + LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from "./layout-base-common"; @@ -25,6 +25,10 @@ export class LayoutBase extends LayoutBaseCommon { console.warn(`clipToBounds with value false is not supported on Android. You can use this.android.getParent().setClipChildren(false) as an alternative`); } + [isPassThroughParentEnabledProperty.setNative](value: boolean) { + (this.nativeViewProtected).setPassThroughParent(value); + } + [paddingTopProperty.getDefault](): Length { return { value: this._defaultPaddingTop, unit: "px" }; } diff --git a/tns-core-modules/ui/layouts/layout-base.d.ts b/tns-core-modules/ui/layouts/layout-base.d.ts index ded023594..4024b9209 100644 --- a/tns-core-modules/ui/layouts/layout-base.d.ts +++ b/tns-core-modules/ui/layouts/layout-base.d.ts @@ -96,6 +96,14 @@ export class LayoutBase extends CustomLayoutView { * Gets or sets a value indicating whether to clip the content of this layout. */ clipToBounds: boolean; + + /** + * Gets or sets a value indicating whether touch event should pass through to a parent view of the + * layout container in case an interactive child view did not handle it. + * Default value of this property is false. This does not affect the appearance of the view. + */ + isPassThroughParentEnabled: boolean; } export const clipToBoundsProperty: Property; +export const isPassThroughParentEnabledProperty: Property; diff --git a/tns-core-modules/ui/layouts/layout-base.ios.ts b/tns-core-modules/ui/layouts/layout-base.ios.ts index e0ff79080..900c1200c 100644 --- a/tns-core-modules/ui/layouts/layout-base.ios.ts +++ b/tns-core-modules/ui/layouts/layout-base.ios.ts @@ -1,4 +1,6 @@ -import { LayoutBaseCommon, clipToBoundsProperty, View } from "./layout-base-common"; +import { + LayoutBaseCommon, clipToBoundsProperty, isPassThroughParentEnabledProperty, View +} from "./layout-base-common"; export * from "./layout-base-common"; @@ -34,4 +36,8 @@ export class LayoutBase extends LayoutBaseCommon { [clipToBoundsProperty.setNative](value: boolean) { this._setNativeClipToBounds(); } + + [isPassThroughParentEnabledProperty.setNative](value: boolean) { + (this.nativeViewProtected).setPassThroughParent(value); + } } \ No newline at end of file