Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.ui.test
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.util.lerp
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.roundToLong
/**
* The receiver scope of the touch input injection lambda from [performTouchInput].
*
* The functions in [TouchInjectionScope] can roughly be divided into two groups: full gestures
* and individual touch events. The individual touch events are: [down], [move] and friends, [up],
* [cancel] and [advanceEventTime]. Full gestures are all the other functions, like
* [click][TouchInjectionScope.click], [doubleClick][TouchInjectionScope.doubleClick],
* [swipe][TouchInjectionScope.swipe], etc. These are built on top of the individual events and
* serve as a good example on how you can build your own full gesture functions.
*
* A touch gesture is started with a [down] event, followed by a sequence of [move] events and
* finally an [up] event, optionally combined with more sets of [down] and [up] events for
* multi-touch gestures. Most methods accept a pointerId to specify which pointer (finger) the
* event applies to. Movement can be expressed absolutely with [moveTo] and [updatePointerTo], or
* relative to the current pointer position with [moveBy] and [updatePointerBy]. The `moveTo/By`
* methods enqueue an event immediately, while the `updatePointerTo/By` methods don't. This
* allows you to update the position of multiple pointers in a single [move] event for
* multi-touch gestures. Touch gestures can be cancelled with [cancel]. All events, regardless
* the method used, will always contain the current position of _all_ pointers.
*
* The entire event injection state is shared between all `perform.*Input` methods, meaning you
* can continue an unfinished touch gesture in a subsequent invocation of [performTouchInput] or
* [performMultiModalInput]. Note however that while the pointer positions are retained across
* invocation of `perform.*Input` methods, they are always manipulated in the current node's
* local coordinate system. That means that two subsequent invocations of [performTouchInput] on
* different nodes will report a different [currentPosition], even though it is actually the same
* position on the screen.
*
* All events sent by these methods are batched together and sent as a whole after
* [performTouchInput] has executed its code block. Because gestures don't have to be defined all
* in the same [performTouchInput] block, keep in mind that while the gesture is not complete,
* all code you execute in between these blocks will be executed while imaginary fingers are
* actively touching the screen.
*
* Example usage:
* ```
* onNodeWithTag("myWidget")
* .performTouchInput {
* click(center)
* }
*
* onNodeWithTag("myWidget")
* // Perform an L-shaped gesture
* .performTouchInput {
* down(topLeft)
* move(topLeft + percentOffset(0f, .1f))
* move(topLeft + percentOffset(0f, .2f))
* move(topLeft + percentOffset(0f, .3f))
* move(topLeft + percentOffset(0f, .4f))
* move(centerLeft)
* move(centerLeft + percentOffset(.1f, 0f))
* move(centerLeft + percentOffset(.2f, 0f))
* move(centerLeft + percentOffset(.3f, 0f))
* move(centerLeft + percentOffset(.4f, 0f))
* move(center)
* up()
* }
* ```
*
* @see InjectionScope
*/
interface TouchInjectionScope : InjectionScope {
/**
* Returns the current position of the given [pointerId]. The default [pointerId] is 0. The
* position is returned in the local coordinate system of the node with which we're
* interacting. (0, 0) is the top left corner of the node.
*/
fun currentPosition(pointerId: Int = 0): Offset?
/**
* Sends a down event for the pointer with the given [pointerId] at [position] on the
* associated node. The [position] is in the node's local coordinate system, where (0, 0) is
* the top left corner of the node.
*
* If no pointers are down yet, this will start a new touch gesture. If a gesture is already
* in progress, this event is sent at the same timestamp as the last event. If the given
* pointer is already down, an [IllegalArgumentException] will be thrown.
*
* @param pointerId The id of the pointer, can be any number not yet in use by another pointer
* @param position The position of the down event, in the node's local coordinate system
*/
fun down(pointerId: Int, position: Offset)
/**
* Sends a down event for the default pointer at [position] on the associated node. The
* [position] is in the node's local coordinate system, where (0, 0) is the top left corner
* of the node. The default pointer has `pointerId = 0`.
*
* If no pointers are down yet, this will start a new touch gesture. If a gesture is already
* in progress, this event is sent at the same timestamp as the last event. If the default
* pointer is already down, an [IllegalArgumentException] will be thrown.
*
* @param position The position of the down event, in the node's local coordinate system
*/
fun down(position: Offset) {
down(0, position)
}
/**
* Sends a move event [delayMillis] after the last sent event on the associated node, with
* the position of the pointer with the given [pointerId] updated to [position]. The
* [position] is in the node's local coordinate system, where (0, 0) is the top left corner
* of the node.
*
* If the pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param pointerId The id of the pointer to move, as supplied in [down]
* @param position The new position of the pointer, in the node's local coordinate system
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun moveTo(pointerId: Int, position: Offset, delayMillis: Long = eventPeriodMillis) {
updatePointerTo(pointerId, position)
move(delayMillis)
}
/**
* Sends a move event [delayMillis] after the last sent event on the associated node, with
* the position of the default pointer updated to [position]. The [position] is in the node's
* local coordinate system, where (0, 0) is the top left corner of the node. The default
* pointer has `pointerId = 0`.
*
* If the default pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param position The new position of the pointer, in the node's local coordinate system
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun moveTo(position: Offset, delayMillis: Long = eventPeriodMillis) {
moveTo(0, position, delayMillis)
}
/**
* Updates the position of the pointer with the given [pointerId] to the given [position], but
* does not send a move event. The move event can be sent with [move]. The [position] is in
* the node's local coordinate system, where (0.px, 0.px) is the top left corner of the
* node.
*
* If the pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param pointerId The id of the pointer to move, as supplied in [down]
* @param position The new position of the pointer, in the node's local coordinate system
*/
fun updatePointerTo(pointerId: Int, position: Offset)
/**
* Sends a move event [delayMillis] after the last sent event on the associated node, with
* the position of the pointer with the given [pointerId] moved by the given [delta].
*
* If the pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param pointerId The id of the pointer to move, as supplied in [down]
* @param delta The position for this move event, relative to the current position of the
* pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's
* x-position, and subtract 10.px from the pointer's y-position.
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun moveBy(pointerId: Int, delta: Offset, delayMillis: Long = eventPeriodMillis) {
updatePointerBy(pointerId, delta)
move(delayMillis)
}
/**
* Sends a move event [delayMillis] after the last sent event on the associated node, with
* the position of the default pointer moved by the given [delta]. The default pointer has
* `pointerId = 0`.
*
* If the pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param delta The position for this move event, relative to the current position of the
* pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's
* x-position, and subtract 10.px from the pointer's y-position.
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun moveBy(delta: Offset, delayMillis: Long = eventPeriodMillis) {
moveBy(0, delta, delayMillis)
}
/**
* Updates the position of the pointer with the given [pointerId] by the given [delta], but
* does not send a move event. The move event can be sent with [move].
*
* If the pointer is not yet down, an [IllegalArgumentException] will be thrown.
*
* @param pointerId The id of the pointer to move, as supplied in [down]
* @param delta The position for this move event, relative to the last sent position of the
* pointer. For example, `delta = Offset(10.px, -10.px) will add 10.px to the pointer's
* x-position, and subtract 10.px from the pointer's y-position.
*/
fun updatePointerBy(pointerId: Int, delta: Offset) {
// Ignore currentPosition of null here, let updatePointerTo generate the error
val position = (currentPosition(pointerId) ?: Offset.Zero) + delta
updatePointerTo(pointerId, position)
}
/**
* Sends a move event [delayMillis] after the last sent event without updating any of the
* pointer positions. This can be useful when batching movement of multiple pointers
* together, which can be done with [updatePointerTo] and [updatePointerBy].
*
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun move(delayMillis: Long = eventPeriodMillis)
/**
* Sends a move event [delayMillis] after the last sent event without updating any of the
* pointer positions.
*
* This overload supports gestures with multiple pointers.
*
* @param relativeHistoricalTimes Time of each historical event, as a millisecond relative to
* the time the actual event is sent. For example, -10L means 10ms earlier.
* @param historicalCoordinates Coordinates of each historical event, in the same coordinate
* space as [moveTo]. The outer list must have the same size as the number of pointers in the
* event, and each inner list must have the same size as [relativeHistoricalTimes].
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
@ExperimentalTestApi
fun moveWithHistoryMultiPointer(
relativeHistoricalTimes: List,
historicalCoordinates: List>,
delayMillis: Long = eventPeriodMillis
)
/**
* Sends a move event [delayMillis] after the last sent event without updating any of the
* pointer positions.
*
* This overload is a convenience method for the common case where the gesture only has one
* pointer.
*
* @param relativeHistoricalTimes Time of each historical event, as a millisecond relative to
* the time the actual event is sent. For example, -10L means 10ms earlier.
* @param historicalCoordinates Coordinates of each historical event, in the same coordinate
* space as [moveTo]. The list must have the same size as [relativeHistoricalTimes].
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
@ExperimentalTestApi
fun moveWithHistory(
relativeHistoricalTimes: List,
historicalCoordinates: List,
delayMillis: Long = eventPeriodMillis
) = moveWithHistoryMultiPointer(
relativeHistoricalTimes,
listOf(historicalCoordinates),
delayMillis
)
/**
* Sends an up event for the pointer with the given [pointerId], or the default pointer if
* [pointerId] is omitted, on the associated node.
*
* @param pointerId The id of the pointer to lift up, as supplied in [down]
*/
fun up(pointerId: Int = 0)
/**
* Sends a cancel event [delayMillis] after the last sent event to cancel the current
* gesture. The cancel event contains the current position of all active pointers.
*
* @param delayMillis The time between the last sent event and this event.
* [eventPeriodMillis] by default.
*/
fun cancel(delayMillis: Long = eventPeriodMillis)
}
internal class TouchInjectionScopeImpl(
private val baseScope: MultiModalInjectionScopeImpl
) : TouchInjectionScope, InjectionScope by baseScope {
private val inputDispatcher get() = baseScope.inputDispatcher
private fun localToRoot(position: Offset) = baseScope.localToRoot(position)
override fun currentPosition(pointerId: Int): Offset? {
val positionInRoot = inputDispatcher.getCurrentTouchPosition(pointerId) ?: return null
return baseScope.rootToLocal(positionInRoot)
}
override fun down(pointerId: Int, position: Offset) {
val positionInRoot = localToRoot(position)
inputDispatcher.enqueueTouchDown(pointerId, positionInRoot)
}
override fun updatePointerTo(pointerId: Int, position: Offset) {
val positionInRoot = localToRoot(position)
inputDispatcher.updateTouchPointer(pointerId, positionInRoot)
}
override fun move(delayMillis: Long) {
advanceEventTime(delayMillis)
inputDispatcher.enqueueTouchMove()
}
@ExperimentalTestApi
override fun moveWithHistoryMultiPointer(
relativeHistoricalTimes: List,
historicalCoordinates: List>,
delayMillis: Long
) {
repeat(relativeHistoricalTimes.size) {
check(relativeHistoricalTimes[it] < 0) {
"Relative historical times should be negative, in order to be in the past" +
"(offset $it was: ${relativeHistoricalTimes[it]})"
}
check(relativeHistoricalTimes[it] >= -delayMillis) {
"Relative historical times should not be earlier than the previous event " +
"(offset $it was: ${relativeHistoricalTimes[it]}, ${-delayMillis})"
}
}
advanceEventTime(delayMillis)
inputDispatcher.enqueueTouchMoves(relativeHistoricalTimes, historicalCoordinates)
}
override fun up(pointerId: Int) {
inputDispatcher.enqueueTouchUp(pointerId)
}
override fun cancel(delayMillis: Long) {
advanceEventTime(delayMillis)
inputDispatcher.enqueueTouchCancel()
}
}
/**
* Performs a click gesture (aka a tap) on the associated node.
*
* The click is done at the given [position], or in the [center] if the [position] is omitted.
* The [position] is in the node's local coordinate system, where (0, 0) is the top left corner
* of the node.
*
* @param position The position where to click, in the node's local coordinate system. If
* omitted, the [center] of the node will be used.
*/
fun TouchInjectionScope.click(position: Offset = center) {
down(position)
move()
up()
}
/**
* Performs a long click gesture (aka a long press) on the associated node.
*
* The long click is done at the given [position], or in the [center] if the [position] is
* omitted. By default, the [durationMillis] of the press is 100ms longer than the minimum
* required duration for a long press. The [position] is in the node's local coordinate system,
* where (0, 0) is the top left corner of the node.
*
* @param position The position of the long click, in the node's local coordinate system. If
* omitted, the [center] of the node will be used.
* @param durationMillis The time between the down and the up event
*/
fun TouchInjectionScope.longClick(
position: Offset = center,
durationMillis: Long = viewConfiguration.longPressTimeoutMillis + 100
) {
require(durationMillis >= viewConfiguration.longPressTimeoutMillis) {
"Long click must have a duration of at least ${viewConfiguration.longPressTimeoutMillis}ms"
}
swipe(position, position, durationMillis)
}
// The average of min and max is a safe default
private val ViewConfiguration.defaultDoubleTapDelayMillis: Long
get() = (doubleTapMinTimeMillis + doubleTapTimeoutMillis) / 2
/**
* Performs a double click gesture (aka a double tap) on the associated node.
*
* The double click is done at the given [position] or in the [center] if the [position] is
* omitted. By default, the [delayMillis] between the first and the second click is half way in
* between the minimum and maximum required delay for a double click. The [position] is in the
* node's local coordinate system, where (0, 0) is the top left corner of the node.
*
* @param position The position of the double click, in the node's local coordinate system.
* If omitted, the [center] position will be used.
* @param delayMillis The time between the up event of the first click and the down event of the
* second click
*/
fun TouchInjectionScope.doubleClick(
position: Offset = center,
delayMillis: Long = viewConfiguration.defaultDoubleTapDelayMillis
) {
require(delayMillis >= viewConfiguration.doubleTapMinTimeMillis) {
"Time between clicks in double click must be at least " +
"${viewConfiguration.doubleTapMinTimeMillis}ms"
}
require(delayMillis < viewConfiguration.doubleTapTimeoutMillis) {
"Time between clicks in double click must be smaller than " +
"${viewConfiguration.doubleTapTimeoutMillis}ms"
}
click(position)
advanceEventTime(delayMillis)
click(position)
}
/**
* Performs a swipe gesture on the associated node.
*
* The motion events are linearly interpolated between [start] and [end]. The coordinates are in
* the node's local coordinate system, where (0, 0) is the top left corner of the node. The
* default duration is 200 milliseconds.
*
* @param start The start position of the gesture, in the node's local coordinate system
* @param end The end position of the gesture, in the node's local coordinate system
* @param durationMillis The duration of the gesture
*/
fun TouchInjectionScope.swipe(
start: Offset,
end: Offset,
durationMillis: Long = 200
) {
val durationFloat = durationMillis.toFloat()
swipe(
curve = { lerp(start, end, it / durationFloat) },
durationMillis = durationMillis
)
}
/**
* Performs a swipe gesture on the associated node.
*
* The swipe follows the [curve] from 0 till [durationMillis]. Will force sampling of an event at
* all times defined in [keyTimes]. The time between events is kept as close to
* [eventPeriodMillis][InjectionScope.eventPeriodMillis] as possible, given the constraints. The
* coordinates are in the node's local coordinate system, where (0, 0) is the top left corner of
* the node. The default duration is 200 milliseconds.
*
* @param curve The function that defines the position of the gesture over time
* @param durationMillis The duration of the gesture
* @param keyTimes An optional list of timestamps in milliseconds at which a move event must
* be sampled
*/
fun TouchInjectionScope.swipe(
curve: (Long) -> Offset,
durationMillis: Long,
keyTimes: List = emptyList()
) {
@OptIn(ExperimentalTestApi::class)
multiTouchSwipe(listOf(curve), durationMillis, keyTimes)
}
/**
* Performs a multi touch swipe gesture on the associated node.
*
* Each pointer follows [curves][i] from 0 till [durationMillis]. Sampling of an event is
* forced at all times defined in [keyTimes]. The time between events is kept as close to
* [eventPeriodMillis][InjectionScope.eventPeriodMillis] as possible, given the constraints. The
* coordinates are in the node's local coordinate system, where (0, 0) is the top left corner of
* the node. The default duration is 200 milliseconds.
*
* Will stay experimental until support has been added to start and end each pointer at
* different times.
*
* @param curves The functions that define the position of the gesture over time
* @param durationMillis The duration of the gesture
* @param keyTimes An optional list of timestamps in milliseconds at which a move event must
* be sampled
*/
@ExperimentalTestApi
fun TouchInjectionScope.multiTouchSwipe(
curves: List<(Long) -> Offset>,
durationMillis: Long,
keyTimes: List = emptyList()
) {
val startTime = 0L
val endTime = durationMillis
// Validate input
require(durationMillis >= 1) {
"duration must be at least 1 millisecond, not $durationMillis"
}
val validRange = startTime..endTime
require(keyTimes.all { it in validRange }) {
"keyTimes contains timestamps out of range [$startTime..$endTime]: $keyTimes"
}
require(keyTimes.asSequence().zipWithNext { a, b -> a <= b }.all { it }) {
"keyTimes must be sorted: $keyTimes"
}
// Send down events
curves.forEachIndexed { i, curve ->
down(i, curve(startTime))
}
// Send move events between each consecutive pair in [t0, ..keyTimes, tN]
var currTime = startTime
var key = 0
while (currTime < endTime) {
// advance key
while (key < keyTimes.size && keyTimes[key] <= currTime) {
key++
}
// send events between t and next keyTime
val tNext = if (key < keyTimes.size) keyTimes[key] else endTime
sendMultiTouchSwipeSegment(curves, currTime, tNext)
currTime = tNext
}
// And end with up events
repeat(curves.size) {
up(it)
}
}
/**
* Generates move events between `f([t0])` and `f([tN])` during the time window `(t0, tN]`, for
* each `f` in [fs], following the curves defined by each `f`. The number of events sent
* (#numEvents) is such that the time between each event is as close to
* [eventPeriodMillis][InputDispatcher.eventPeriodMillis] as possible, but at least 1. The first
* event is sent at time `downTime + (tN - t0) / #numEvents`, the last event is sent at time tN.
*
* @param fs The functions that define the coordinates of the respective gestures over time
* @param t0 The start time of this segment of the swipe, in milliseconds relative to downTime
* @param tN The end time of this segment of the swipe, in milliseconds relative to downTime
*/
private fun TouchInjectionScope.sendMultiTouchSwipeSegment(
fs: List<(Long) -> Offset>,
t0: Long,
tN: Long
) {
var step = 0
// How many steps will we take between t0 and tN? At least 1, and a number that will
// bring as as close to eventPeriod as possible
val steps = max(1, ((tN - t0) / eventPeriodMillis.toFloat()).roundToInt())
var tPrev = t0
while (step++ < steps) {
val progress = step / steps.toFloat()
val t = lerp(t0, tN, progress)
fs.forEachIndexed { i, f ->
updatePointerTo(i, f(t))
}
move(t - tPrev)
tPrev = t
}
}
/**
* Performs a pinch gesture on the associated node.
*
* For each pair of start and end [Offset]s, the motion events are linearly interpolated. The
* coordinates are in the node's local coordinate system where (0, 0) is the top left corner of
* the node. The default duration is 400 milliseconds.
*
* @param start0 The start position of the first gesture in the node's local coordinate system
* @param end0 The end position of the first gesture in the node's local coordinate system
* @param start1 The start position of the second gesture in the node's local coordinate system
* @param end1 The end position of the second gesture in the node's local coordinate system
* @param durationMillis the duration of the gesture
*/
fun TouchInjectionScope.pinch(
start0: Offset,
end0: Offset,
start1: Offset,
end1: Offset,
durationMillis: Long = 400
) {
val durationFloat = durationMillis.toFloat()
@OptIn(ExperimentalTestApi::class)
multiTouchSwipe(
listOf(
{ lerp(start0, end0, it / durationFloat) },
{ lerp(start1, end1, it / durationFloat) }
),
durationMillis
)
}
/**
* Performs a swipe gesture on the associated node such that it ends with the given [endVelocity].
*
* The swipe will go through [start] at t=0 and through [end] at t=[durationMillis]. In between,
* the swipe will go monotonically from [start] and [end], but not strictly. Due to imprecision,
* no guarantees can be made for the actual velocity at the end of the gesture, but generally it
* is within 0.1 of the desired velocity.
*
* When a swipe cannot be created that results in the desired velocity (because the input is too
* restrictive), an exception will be thrown with suggestions to fix the input.
*
* The coordinates are in the node's local coordinate system, where (0, 0) is the top left corner
* of the node. The default duration is calculated such that a feasible swipe can be created that
* ends in the given velocity.
*
* @param start The start position of the gesture, in the node's local coordinate system
* @param end The end position of the gesture, in the node's local coordinate system
* @param endVelocity The velocity of the gesture at the moment it ends in px/second. Must be
* positive.
* @param durationMillis The duration of the gesture in milliseconds. Must be long enough that at
* least 3 input events are generated, which happens with a duration of 40ms or more. If omitted,
* a duration is calculated such that a valid swipe with velocity can be created.
*
* @throws IllegalArgumentException When no swipe can be generated that will result in the desired
* velocity. The error message will suggest changes to the input parameters such that a swipe
* will become feasible.
*/
fun TouchInjectionScope.swipeWithVelocity(
start: Offset,
end: Offset,
/*@FloatRange(from = 0.0)*/
endVelocity: Float,
durationMillis: Long = VelocityPathFinder.calculateDefaultDuration(start, end, endVelocity)
) {
require(endVelocity >= 0f) {
"Velocity cannot be $endVelocity, it must be positive"
}
require(eventPeriodMillis < 40) {
"InputDispatcher.eventPeriod must be smaller than 40ms in order to generate velocities"
}
val minimumDuration = ceil(2.5f * eventPeriodMillis).roundToLong()
require(durationMillis >= minimumDuration) {
"Duration must be at least ${minimumDuration}ms because " +
"velocity requires at least 3 input events"
}
val pathFinder = VelocityPathFinder(start, end, endVelocity, durationMillis)
swipe(pathFinder.generateFunction(), durationMillis)
}
/**
* Performs a swipe up gesture along `x = [centerX]` of the associated node, from [startY] till
* [endY], taking [durationMillis] milliseconds.
*
* @param startY The y-coordinate of the start of the swipe. Must be greater than or equal to the
* [endY]. By default the [bottom] of the node.
* @param endY The y-coordinate of the end of the swipe. Must be less than or equal to the
* [startY]. By default the [top] of the node.
* @param durationMillis The duration of the swipe. By default 200 milliseconds.
*/
fun TouchInjectionScope.swipeUp(
startY: Float = bottom,
endY: Float = top,
durationMillis: Long = 200
) {
require(startY >= endY) {
"startY=$startY needs to be greater than or equal to endY=$endY"
}
val start = Offset(centerX, startY)
val end = Offset(centerX, endY)
swipe(start, end, durationMillis)
}
/**
* Performs a swipe down gesture along `x = [centerX]` of the associated node, from [startY] till
* [endY], taking [durationMillis] milliseconds.
*
* @param startY The y-coordinate of the start of the swipe. Must be less than or equal to the
* [endY]. By default the [top] of the node.
* @param endY The y-coordinate of the end of the swipe. Must be greater than or equal to the
* [startY]. By default the [bottom] of the node.
* @param durationMillis The duration of the swipe. By default 200 milliseconds.
*/
fun TouchInjectionScope.swipeDown(
startY: Float = top,
endY: Float = bottom,
durationMillis: Long = 200
) {
require(startY <= endY) {
"startY=$startY needs to be less than or equal to endY=$endY"
}
val start = Offset(centerX, startY)
val end = Offset(centerX, endY)
swipe(start, end, durationMillis)
}
/**
* Performs a swipe left gesture along `y = [centerY]` of the associated node, from [startX] till
* [endX], taking [durationMillis] milliseconds.
*
* @param startX The x-coordinate of the start of the swipe. Must be greater than or equal to the
* [endX]. By default the [right] of the node.
* @param endX The x-coordinate of the end of the swipe. Must be less than or equal to the
* [startX]. By default the [left] of the node.
* @param durationMillis The duration of the swipe. By default 200 milliseconds.
*/
fun TouchInjectionScope.swipeLeft(
startX: Float = right,
endX: Float = left,
durationMillis: Long = 200
) {
require(startX >= endX) {
"startX=$startX needs to be greater than or equal to endX=$endX"
}
val start = Offset(startX, centerY)
val end = Offset(endX, centerY)
swipe(start, end, durationMillis)
}
/**
* Performs a swipe right gesture along `y = [centerY]` of the associated node, from [startX]
* till [endX], taking [durationMillis] milliseconds.
*
* @param startX The x-coordinate of the start of the swipe. Must be less than or equal to the
* [endX]. By default the [left] of the node.
* @param endX The x-coordinate of the end of the swipe. Must be greater than or equal to the
* [startX]. By default the [right] of the node.
* @param durationMillis The duration of the swipe. By default 200 milliseconds.
*/
fun TouchInjectionScope.swipeRight(
startX: Float = left,
endX: Float = right,
durationMillis: Long = 200
) {
require(startX <= endX) {
"startX=$startX needs to be less than or equal to endX=$endX"
}
val start = Offset(startX, centerY)
val end = Offset(endX, centerY)
swipe(start, end, durationMillis)
}