Skip to content

Commit

Permalink
Fix mozilla-mobile#9614 - Add a new InputResultDetail
Browse files Browse the repository at this point in the history
This will serve the following purposes:
- wrapper for all the new data from GeckoView's onTouchEventForDetailResult
- filters out values not in range (eg: GV's INPUT_RESULT_IGNORED)
- controls how the data can be updated and offers clear APIs for querying this
data without needing to know about the implementation specifics.
  • Loading branch information
Mugurell committed Mar 24, 2021
1 parent 0bc7edf commit 07ab0b7
Show file tree
Hide file tree
Showing 2 changed files with 533 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.concept.engine

import android.view.MotionEvent
import androidx.annotation.VisibleForTesting

// The below top-level values are following the same from [org.mozilla.geckoview.PanZoomController]

/**
* The the content has no scrollable element.
*
* @see [InputResultDetail.isTouchUnhandled]
*/
@VisibleForTesting
internal const val INPUT_UNHANDLED = 0

/**
* The touch event is consumed by the [EngineView]
*
* @see [InputResultDetail.isTouchHandledByBrowser]
*/
@VisibleForTesting
internal const val INPUT_HANDLED = 1

/**
* The touch event is consumed by the website through it's own touch listeners.
*
* @see [InputResultDetail.isTouchHandledByWebsite]
*/
@VisibleForTesting
internal const val INPUT_HANDLED_CONTENT = 2

/**
* The website content is not scrollable.
*/
@VisibleForTesting
internal const val SCROLL_DIRECTIONS_NONE = 0

/**
* The website content can be scrolled to the top.
*
* @see [InputResultDetail.canScrollToTop]
*/
@VisibleForTesting
internal const val SCROLL_DIRECTIONS_TOP = 1 shl 0

/**
* The website content can be scrolled to the right.
*
* @see [InputResultDetail.canScrollToRight]
*/
@VisibleForTesting
internal const val SCROLL_DIRECTIONS_RIGHT = 1 shl 1

/**
* The website content can be scrolled to the bottom.
*
* @see [InputResultDetail.canScrollToBottom]
*/
@VisibleForTesting
internal const val SCROLL_DIRECTIONS_BOTTOM = 1 shl 2

/**
* The website content can be scrolled to the left.
*
* @see [InputResultDetail.canScrollToLeft]
*/
@VisibleForTesting
internal const val SCROLL_DIRECTIONS_LEFT = 1 shl 3

/**
* The website content cannot be overscrolled.
*/
@VisibleForTesting
internal const val OVERSCROLL_DIRECTIONS_NONE = 0

/**
* The website content can be overscrolled horizontally.
*
* @see [InputResultDetail.canOverscrollRight]
* @see [InputResultDetail.canOverscrollLeft]
*/
@VisibleForTesting
internal const val OVERSCROLL_DIRECTIONS_HORIZONTAL = 1 shl 0

/**
* The website content can be overscrolled vertically.
*
* @see [InputResultDetail.canOverscrollTop]
* @see [InputResultDetail.canOverscrollBottom]
*/
@VisibleForTesting
internal const val OVERSCROLL_DIRECTIONS_VERTICAL = 1 shl 1

/**
* All data about how a touch will be handled by the browser.
* - whether the event is used for panning/zooming by the browser / by the website or will be ignored.
* - whether the event can scroll the page and in what direction.
* - whether the event can overscroll the page and in what direction.
*
* Data wrapped by this class is safe to use after first calling [update] to update the default values.
*
* This class can be reused by calling [reset] to return all data values to it's default value.
*/
@Suppress("TooManyFunctions")
class InputResultDetail {
/**
* Indicates who will use the current [MotionEvent].
*
* @see INPUT_UNHANDLED
* @see INPUT_HANDLED
* @see INPUT_HANDLED_CONTENT
*/
@VisibleForTesting
internal var inputResult: Int = INPUT_UNHANDLED

/**
* Bitwise ORed value of the directions the page can be scrolled to.
*
* This is the same as GeckoView's [org.mozilla.geckoview.PanZoomController.ScrollableDirections].
*/
@VisibleForTesting
internal var scrollDirections: Int = SCROLL_DIRECTIONS_NONE

/**
* Bitwise ORed value of the directions the page can be overscrolled to.
*
* This is the same as GeckoView's [org.mozilla.geckoview.PanZoomController.OverscrollDirections].
*
* The default value is [OVERSCROLL_DIRECTIONS_VERTICAL] and not [OVERSCROLL_DIRECTIONS_NONE] since
* there are cases in which this class can be used before valid values being set and it helps more to have
* overscroll vertically allowed and then stop depending on the values with which this class is updated
* rather than start with a disabled overscroll functionality for the current gesture.
*/
@VisibleForTesting
internal var overscrollDirections: Int = OVERSCROLL_DIRECTIONS_VERTICAL

/**
* The only way to update the values contained by this class.
* Only after calling this method the various getters are safe to use.
*
* This method will filter out unexpected values to ensure data consistency.
*/
fun update(
inputResult: Int? = this.inputResult,
scrollDirections: Int? = this.scrollDirections,
overscrollDirections: Int? = this.overscrollDirections
) {
// The range check automatically checks for null but doesn't yet have a contract to say so.
// As such it it safe to use the not-null assertion operator.

if (inputResult in INPUT_UNHANDLED..INPUT_HANDLED_CONTENT) {
this.inputResult = inputResult!!
}
if (scrollDirections in SCROLL_DIRECTIONS_NONE..(SCROLL_DIRECTIONS_LEFT or (SCROLL_DIRECTIONS_LEFT - 1))) {
this.scrollDirections = scrollDirections!!
}
if (overscrollDirections in
OVERSCROLL_DIRECTIONS_NONE..(OVERSCROLL_DIRECTIONS_VERTICAL or (OVERSCROLL_DIRECTIONS_VERTICAL - 1))) {

this.overscrollDirections = overscrollDirections!!
}
}

/**
* Reset all data to default values.
*/
fun reset() {
inputResult = INPUT_UNHANDLED
scrollDirections = SCROLL_DIRECTIONS_NONE
overscrollDirections = OVERSCROLL_DIRECTIONS_VERTICAL
}

/**
* The [EngineView] handled the last [MotionEvent] to pan or zoom the content.
*/
fun isTouchHandledByBrowser() = inputResult == INPUT_HANDLED

/**
* The website handled the last [MotionEvent] through it's own touch listeners
* and consumed it without the [EngineView] panning or zooming the website
*/
fun isTouchHandledByWebsite() = inputResult == INPUT_HANDLED_CONTENT

/**
* Neither the [EngineView], nor the website will handle this [MotionEvent].
*
* This might happen on a website without touch listeners that is not bigger than the screen
* or when the content has no scrollable element.
*/
fun isTouchUnhandled() = inputResult == INPUT_UNHANDLED

/**
* Whether the width of the webpage exceeds the display and the webpage can be scrolled to left.
*/
fun canScrollToLeft(): Boolean =
inputResult == INPUT_HANDLED &&
scrollDirections and SCROLL_DIRECTIONS_LEFT != 0

/**
* Whether the height of the webpage exceeds the display and the webpage can be scrolled to top.
*/
fun canScrollToTop(): Boolean =
inputResult == INPUT_HANDLED &&
scrollDirections and SCROLL_DIRECTIONS_TOP != 0

/**
* Whether the width of the webpage exceeds the display and the webpage can be scrolled to right.
*/
fun canScrollToRight(): Boolean =
inputResult == INPUT_HANDLED &&
scrollDirections and SCROLL_DIRECTIONS_RIGHT != 0

/**
* Whether the height of the webpage exceeds the display and the webpage can be scrolled to bottom.
*/
fun canScrollToBottom(): Boolean =
inputResult == INPUT_HANDLED &&
scrollDirections and SCROLL_DIRECTIONS_BOTTOM != 0

/**
* Whether the webpage can be overscrolled to the left.
*
* @return `true` if the page is already scrolled to the left most part
* and the touch event is not handled by the webpage.
*/
fun canOverscrollLeft(): Boolean =
inputResult != INPUT_HANDLED_CONTENT &&
(scrollDirections and SCROLL_DIRECTIONS_LEFT == 0) &&
(overscrollDirections and OVERSCROLL_DIRECTIONS_HORIZONTAL != 0)

/**
* Whether the webpage can be overscrolled to the top.
*
* @return `true` if the page is already scrolled to the top most part
* and the touch event is not handled by the webpage.
*/
fun canOverscrollTop(): Boolean =
inputResult != INPUT_HANDLED_CONTENT &&
(scrollDirections and SCROLL_DIRECTIONS_TOP == 0) &&
(overscrollDirections and OVERSCROLL_DIRECTIONS_VERTICAL != 0)

/**
* Whether the webpage can be overscrolled to the right.
*
* @return `true` if the page is already scrolled to the right most part
* and the touch event is not handled by the webpage.
*/
fun canOverscrollRight(): Boolean =
inputResult != INPUT_HANDLED_CONTENT &&
(scrollDirections and SCROLL_DIRECTIONS_RIGHT == 0) &&
(overscrollDirections and OVERSCROLL_DIRECTIONS_HORIZONTAL != 0)

/**
* Whether the webpage can be overscrolled to the bottom.
*
* @return `true` if the page is already scrolled to the bottom most part
* and the touch event is not handled by the webpage.
*/
fun canOverscrollBottom(): Boolean =
inputResult != INPUT_HANDLED_CONTENT &&
(scrollDirections and SCROLL_DIRECTIONS_BOTTOM == 0) &&
(overscrollDirections and OVERSCROLL_DIRECTIONS_VERTICAL != 0)
}
Loading

0 comments on commit 07ab0b7

Please sign in to comment.