[go: nahoru, domu]

blob: cd7504ea8e7b8221edba027a43aee47f503968e0 [file] [log] [blame]
/*
* Copyright 2018 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.ui.widgets.gesturedetector
import androidx.ui.foundation.Key
import androidx.ui.foundation.assertions.FlutterError
import androidx.ui.foundation.diagnostics.DiagnosticLevel
import androidx.ui.foundation.diagnostics.DiagnosticPropertiesBuilder
import androidx.ui.foundation.diagnostics.DiagnosticsNode
import androidx.ui.foundation.diagnostics.EnumProperty
import androidx.ui.foundation.diagnostics.IterableProperty
import androidx.ui.gestures.drag_details.DragDownDetails
import androidx.ui.gestures.drag_details.DragEndDetails
import androidx.ui.gestures.drag_details.DragStartDetails
import androidx.ui.gestures.drag_details.DragUpdateDetails
import androidx.ui.gestures.drag_details.GestureDragDownCallback
import androidx.ui.gestures.drag_details.GestureDragStartCallback
import androidx.ui.gestures.drag_details.GestureDragUpdateCallback
import androidx.ui.gestures.events.PointerDownEvent
import androidx.ui.gestures.long_press.GestureLongPressCallback
import androidx.ui.gestures.long_press.LongPressGestureRecognizer
import androidx.ui.gestures.monodrag.GestureDragCancelCallback
import androidx.ui.gestures.monodrag.GestureDragEndCallback
import androidx.ui.gestures.monodrag.HorizontalDragGestureRecognizer
import androidx.ui.gestures.monodrag.PanGestureRecognizer
import androidx.ui.gestures.monodrag.VerticalDragGestureRecognizer
import androidx.ui.gestures.multitap.DoubleTapGestureRecognizer
import androidx.ui.gestures.recognizer.GestureRecognizer
import androidx.ui.gestures.scale.GestureScaleEndCallback
import androidx.ui.gestures.scale.GestureScaleStartCallback
import androidx.ui.gestures.scale.GestureScaleUpdateCallback
import androidx.ui.gestures.scale.ScaleGestureRecognizer
import androidx.ui.gestures.tap.GestureTapCallback
import androidx.ui.gestures.tap.GestureTapCancelCallback
import androidx.ui.gestures.tap.GestureTapDownCallback
import androidx.ui.gestures.tap.GestureTapUpCallback
import androidx.ui.gestures.tap.TapDownDetails
import androidx.ui.gestures.tap.TapGestureRecognizer
import androidx.ui.gestures.tap.TapUpDetails
import androidx.ui.rendering.obj.RenderObject
import androidx.ui.rendering.proxybox.HitTestBehavior
import androidx.ui.rendering.proxybox.RenderSemanticsGestureHandler
import androidx.ui.semantics.SemanticsAction
import androidx.ui.widgets.basic.Listener
import androidx.ui.widgets.framework.BuildContext
import androidx.ui.widgets.framework.SingleChildRenderObjectWidget
import androidx.ui.widgets.framework.State
import androidx.ui.widgets.framework.StatefulWidget
import androidx.ui.widgets.framework.StatelessWidget
import androidx.ui.widgets.framework.Widget
import kotlin.reflect.KClass
// TODO(Migration/shepshapard): Porting tests requires widget testing infastructure.
/**
* Factory for creating gesture recognizers.
*
* `T` is the type of gesture recognizer this class manages.
*
* Used by [RawGestureDetector.gestures].
*/
abstract class GestureRecognizerFactory<T : GestureRecognizer> {
/** Must return an instance of T. */
abstract fun constructor(): T
/**
* Must configure the given instance (which will have been created by
* `constructor`).
*
* This normally means setting the callbacks.
*/
abstract fun initializer(instance: T)
// TODO(Migration/shepshapard): Is this possible?
// open fun _debugAssertTypeMatches() : Boolean {
// assert(type == T, "GestureRecognizerFactory of type $T was used where type $type was specified.")
// return true
// }
}
/** Signature for closures that implement [GestureRecognizerFactory.constructor]. */
typealias GestureRecognizerFactoryConstructor<T> = () -> T
/** Signature for closures that implement [GestureRecognizerFactory.initializer]. */
typealias GestureRecognizerFactoryInitializer<T> = (T) -> Unit
/**
* Factory for creating gesture recognizers that delegates to callbacks.
*
* Used by [RawGestureDetector.gestures].
*/
class GestureRecognizerFactoryWithHandlers<T : GestureRecognizer>(
private val constructor: GestureRecognizerFactoryConstructor<T>,
private val initializer: GestureRecognizerFactoryInitializer<T>
) : GestureRecognizerFactory<T>() {
override fun constructor(): T {
return constructor.invoke()
}
override fun initializer(instance: T) {
return initializer.invoke(instance)
}
}
/**
* A widget that detects gestures.
*
* Attempts to recognize gestures that correspond to its non-null callbacks.
*
* If this widget has a child, it defers to that child for its sizing behavior.
* If it does not have a child, it grows to fit the parent instead.
*
* By default a GestureDetector with an invisible child ignores touches;
* this behavior can be controlled with [behavior].
*
* GestureDetector also listens for accessibility events and maps
* them to the callbacks. To ignore accessibility events, set
* [excludeFromSemantics] to true.
*
* See <http://flutter.io/gestures/> for additional information.
*
* Material design applications typically react to touches with ink splash
* effects. The [InkWell] class implements this effect and can be used in place
* of a [GestureDetector] for handling taps.
*
* ## Sample code
*
* This example makes a rectangle react to being tapped by setting the
* `_lights` field:
*
* ```dart
* new GestureDetector(
* onTap: () {
* setState(() { _lights = true; });
* },
* child: new Container(
* color: Colors.yellow,
* child: new Text('TURN LIGHTS ON'),
* ),
* )
* ```
*
* ## Debugging
*
* To see how large the hit test box of a [GestureDetector] is for debugging
* purposes, set [debugPaintPointersEnabled] to true.
*
* Pan and scale callbacks cannot be used simultaneously because scale is a
* superset of pan. Simply use the scale callbacks instead.
*
* Horizontal and vertical drag callbacks cannot be used simultaneously
* because a combination of a horizontal and vertical drag is a pan. Simply
* use the pan callbacks instead.
*
* By default, gesture detectors contribute semantic information to the tree
* that is used by assistive technology.
*/
class GestureDetector(
key: Key? = null,
/**
* The widget below this widget in the tree.
*
* {@macro flutter.widgets.child}
*/
val child: Widget?,
/**
* A pointer that might cause a tap has contacted the screen at a particular
* location.
*/
private val onTapDown: GestureTapDownCallback? = null,
/**
* A pointer that will trigger a tap has stopped contacting the screen at a
* particular location.
*/
private val onTapUp: GestureTapUpCallback? = null,
/** A tap has occurred. */
private val onTap: GestureTapCallback? = null,
/**
* The pointer that previously triggered [onTapDown] will not end up causing
* a tap.
*/
private val onTapCancel: GestureTapCancelCallback? = null,
/**
* The user has tapped the screen at the same location twice in quick
* succession.
*/
private val onDoubleTap: GestureTapCallback? = null,
/**
* A pointer has remained in contact with the screen at the same location for
* a long period of time.
*/
private val onLongPress: GestureLongPressCallback? = null,
/** A pointer has contacted the screen and might begin to move vertically. */
private val onVerticalDragDown: GestureDragDownCallback? = null,
/** A pointer has contacted the screen and has begun to move vertically. */
private val onVerticalDragStart: GestureDragStartCallback? = null,
/**
* A pointer that is in contact with the screen and moving vertically has
* moved in the vertical direction.
*/
private val onVerticalDragUpdate: GestureDragUpdateCallback? = null,
/**
* A pointer that was previously in contact with the screen and moving
* vertically is no longer in contact with the screen and was moving at a
* specific velocity when it stopped contacting the screen.
*/
private val onVerticalDragEnd: GestureDragEndCallback? = null,
/**
* The pointer that previously triggered [onVerticalDragDown] did not
* complete.
*/
private val onVerticalDragCancel: GestureDragCancelCallback? = null,
/** A pointer has contacted the screen and might begin to move horizontally. */
private val onHorizontalDragDown: GestureDragDownCallback? = null,
/** A pointer has contacted the screen and has begun to move horizontally. */
private val onHorizontalDragStart: GestureDragStartCallback? = null,
/**
* A pointer that is in contact with the screen and moving horizontally has
* moved in the horizontal direction.
*/
private val onHorizontalDragUpdate: GestureDragUpdateCallback? = null,
/**
* A pointer that was previously in contact with the screen and moving
* horizontally is no longer in contact with the screen and was moving at a
* specific velocity when it stopped contacting the screen.
*/
private val onHorizontalDragEnd: GestureDragEndCallback? = null,
/**
* The pointer that previously triggered [onHorizontalDragDown] did not
* complete.
*/
private val onHorizontalDragCancel: GestureDragCancelCallback? = null,
/** A pointer has contacted the screen and might begin to move. */
private val onPanDown: GestureDragDownCallback? = null,
/** A pointer has contacted the screen and has begun to move. */
private val onPanStart: GestureDragStartCallback? = null,
/** A pointer that is in contact with the screen and moving has moved again. */
private val onPanUpdate: GestureDragUpdateCallback? = null,
/**
* A pointer that was previously in contact with the screen and moving
* is no longer in contact with the screen and was moving at a specific
* velocity when it stopped contacting the screen.
*/
private val onPanEnd: GestureDragEndCallback? = null,
/** The pointer that previously triggered [onPanDown] did not complete. */
private val onPanCancel: GestureDragCancelCallback? = null,
/**
* The pointers in contact with the screen have established a focal point and
* initial scale of 1.0.
*/
private val onScaleStart: GestureScaleStartCallback? = null,
/**
* The pointers in contact with the screen have indicated a new focal point
* and/or scale.
*/
private val onScaleUpdate: GestureScaleUpdateCallback? = null,
/** The pointers are no longer in contact with the screen. */
private val onScaleEnd: GestureScaleEndCallback? = null,
/**
* How this gesture detector should behave during hit testing.
*
* This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
* [HitTestBehavior.translucent] if child is null.
*/
behavior: HitTestBehavior? = null,
/**
* Whether to exclude these gestures from the semantics tree. For
* example, the long-press gesture for showing a tooltip is
* excluded because the tooltip itself is included in the semantics
* tree directly and so having a gesture to show it would result in
* duplication of information.
*/
private val excludeFromSemantics: Boolean = false
) : StatelessWidget(key = key) {
/** How this gesture detector behaves during hit testing. */
val behavior: HitTestBehavior =
behavior ?: if (child != null) {
HitTestBehavior.DEFER_TO_CHILD
} else {
HitTestBehavior.TRANSLUCENT
}
init {
androidx.ui.assert {
val haveVerticalDrag = onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null
val haveHorizontalDrag = onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null
val havePan = onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null
val haveScale = onScaleStart != null ||
onScaleUpdate != null ||
onScaleEnd != null
if (havePan || haveScale) {
if (havePan && haveScale) {
throw FlutterError(
"Incorrect GestureDetector arguments.\n" +
"Having both a pan gesture recognizer and a scale gesture " +
"recognizer is redundant; scale is a superset of pan. Just " +
"use the scale gesture recognizer."
)
}
val recognizer = if (havePan) "pan" else "scale"
if (haveVerticalDrag && haveHorizontalDrag) {
throw FlutterError(
"Incorrect GestureDetector arguments.\n" +
"Simultaneously having a vertical drag gesture recognizer, a " +
"horizontal drag gesture recognizer, and a $recognizer gesture " +
"recognizer will result in the $recognizer gesture recognizer " +
"being ignored, since the other two will catch all drags."
)
}
}
true
}
}
override fun build(context: BuildContext): Widget {
val gestures:
MutableMap<KClass<*>, GestureRecognizerFactoryWithHandlers<out GestureRecognizer>> =
mutableMapOf()
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
TapGestureRecognizer(debugOwner = this)
}, { instance ->
instance.onTap = onTap
instance.onTapUp = onTapUp
instance.onTapDown = onTapDown
instance.onTapCancel = onTapCancel
})
}
if (onDoubleTap != null) {
gestures[DoubleTapGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
DoubleTapGestureRecognizer(debugOwner = this)
}, { instance ->
instance.onDoubleTap = onDoubleTap
})
}
if (onLongPress != null) {
gestures[LongPressGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
LongPressGestureRecognizer(debugOwner = this)
}, { instance ->
instance.onLongPress = onLongPress
})
}
if (onVerticalDragDown != null ||
onVerticalDragStart != null ||
onVerticalDragUpdate != null ||
onVerticalDragEnd != null ||
onVerticalDragCancel != null
) {
gestures[VerticalDragGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
VerticalDragGestureRecognizer(debugOwner = this)
}, { instance ->
instance.apply {
onDown = onVerticalDragDown
onStart = onVerticalDragStart
onUpdate = onVerticalDragUpdate
onEnd = onVerticalDragEnd
onCancel = onVerticalDragCancel
}
})
}
if (onHorizontalDragDown != null ||
onHorizontalDragStart != null ||
onHorizontalDragUpdate != null ||
onHorizontalDragEnd != null ||
onHorizontalDragCancel != null
) {
gestures[HorizontalDragGestureRecognizer::class] =
GestureRecognizerFactoryWithHandlers({
HorizontalDragGestureRecognizer(debugOwner = this)
}, { instance ->
instance.apply {
onDown = onHorizontalDragDown
onStart = onHorizontalDragStart
onUpdate = onHorizontalDragUpdate
onEnd = onHorizontalDragEnd
onCancel = onHorizontalDragCancel
}
})
}
if (onPanDown != null ||
onPanStart != null ||
onPanUpdate != null ||
onPanEnd != null ||
onPanCancel != null
) {
gestures[PanGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
PanGestureRecognizer(debugOwner = this)
}, { instance ->
instance.apply {
onDown = onPanDown
onStart = onPanStart
onUpdate = onPanUpdate
onEnd = onPanEnd
onCancel = onPanCancel
}
})
}
if (onScaleStart != null ||
onScaleUpdate != null ||
onScaleEnd != null
) {
gestures[ScaleGestureRecognizer::class] = GestureRecognizerFactoryWithHandlers({
ScaleGestureRecognizer(debugOwner = this)
}, { instance ->
instance.apply {
onStart = onScaleStart
onUpdate = onScaleUpdate
onEnd = onScaleEnd
}
})
}
return RawGestureDetector(
// TODO(migration/shepshapard): Had to do this nasty casting somewhere. This should be
// improved in the long run.
gestures = gestures as Map<KClass<*>, GestureRecognizerFactory<GestureRecognizer>>,
behavior = behavior,
excludeFromSemantics = excludeFromSemantics,
child = child
)
}
}
// TODO(migration/shepshapard): Convert below sample dart code to kotlin.
/**
* A widget that detects gestures described by the given gesture
* factories.
*
* For common gestures, use a [GestureRecognizer].
* [RawGestureDetector] is useful primarily when developing your
* own gesture recognizers.
*
* Configuring the gesture recognizers requires a carefully constructed map, as
* described in [gestures] and as shown in the example below.
*
* ## Sample code
*
* This example shows how to hook up a [TapGestureRecognizer]. It assumes that
* the code is being used inside a [State] object with a `_last` field that is
* then displayed as the child of the gesture detector.
*
* ```dart
* new RawGestureDetector(
* gestures: <Type, GestureRecognizerFactory>{
* TapGestureRecognizer: new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
* () => new TapGestureRecognizer(),
* (TapGestureRecognizer instance) {
* instance
* .. details) { setState(() { _last = 'down'; }); }
* .. details) { setState(() { _last = 'up'; }); }
* .. { setState(() { _last = 'tap'; }); }
* .. { setState(() { _last = 'cancel'; }); };
* },
* ),
* },
* child: new Container(width: 300.0, height: 300.0, color: Colors.yellow, child: new Text(_last)),
* )
* ```
*
* See also:
*
* * [GestureDetector], a less flexible but much simpler widget that does the same thing.
* * [Listener], a widget that reports raw pointer events.
* * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
*
* By default, gesture detectors contribute semantic information to the tree
* that is used by assistive technology. This can be controlled using
* [excludeFromSemantics].
*/
class RawGestureDetector(
key: Key? = null,
/**
* The widget below this widget in the tree.
*
* {@macro flutter.widgets.child}
*/
val child: Widget? = null,
/**
* The gestures that this widget will attempt to recognize.
*
* This should be a map from [GestureRecognizer] subclasses to
* [GestureRecognizerFactory] subclasses specialized with the same type.
*
* This value can be late-bound at layout time using
* [RawGestureDetectorState.replaceGestureRecognizers].
*/
val gestures: Map<KClass<*>, GestureRecognizerFactory<GestureRecognizer>>,
/**
* How this gesture detector should behave during hit testing.
*
* This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
* [HitTestBehavior.translucent] if child is null.
*/
behavior: HitTestBehavior? = null,
/**
* Whether to exclude these gestures from the semantics tree. For
* example, the long-press gesture for showing a tooltip is
* excluded because the tooltip itself is included in the semantics
* tree directly and so having a gesture to show it would result in
* duplication of information.
*/
val excludeFromSemantics: Boolean = false
) : StatefulWidget(
key = key
) {
/** How this gesture detector behaves during hit testing. */
val behavior: HitTestBehavior =
behavior ?: if (child != null) {
HitTestBehavior.DEFER_TO_CHILD
} else {
HitTestBehavior.TRANSLUCENT
}
override fun createState(): State<StatefulWidget> {
return RawGestureDetectorState(this) as State<StatefulWidget>
}
}
/** State for a [RawGestureDetector]. */
class RawGestureDetectorState(widget: RawGestureDetector) :
State<RawGestureDetector>(widget = widget) {
internal var recognizers: MutableMap<KClass<*>, GestureRecognizer> = mutableMapOf()
override fun initState() {
super.initState()
this.syncAll(widget.gestures)
}
override fun didUpdateWidget(oldWidget: RawGestureDetector) {
super.didUpdateWidget(oldWidget)
this.syncAll(widget.gestures)
}
/**
* This method can be called after the build phase, during the
* layout of the nearest descendant [RenderObjectWidget] of the
* gesture detector, to update the list of active gesture
* recognizers.
*
* The typical use case is [Scrollable]s, which put their viewport
* in their gesture detector, and then need to know the dimensions
* of the viewport and the viewport's child to determine whether
* the gesture detector should be enabled.
*
* The argument should follow the same conventions as
* [RawGestureDetector.gestures]. It acts like a temporary replacement for
* that value until the next build.
*/
// TODO(Migration/shepshapard): The findRenderObject call made in this method is nullable and
// that seems problematic. This method wasn't needed at this time so commented out for now.
/*fun replaceGestureRecognizers(
gestures: MutableMap<KClass<*>, GestureRecognizerFactory<GestureRecognizer>>
) {
androidx.ui.assert {
if (!context!!.findRenderObject()?.owner!!.debugDoingLayout) {
throw FlutterError(
"Unexpected call to replaceGestureRecognizers() method of " +
"RawGestureDetectorState.\n" +
"The replaceGestureRecognizers() method can only be called during " +
"the layout phase. To set the gesture recognizers at other times, " +
"trigger a new build using setState() and provide the new gesture " +
"recognizers as constructor arguments to the corresponding " +
"RawGestureDetector or GestureDetector object."
)
}
true
}
syncAll(gestures)
if (!widget.excludeFromSemantics) {
val semanticsGestureHandler = context!!.findRenderObject()
context!!.visitChildElements { element ->
(element.widget as GestureSemantics).updateHandlers(semanticsGestureHandler)
}
}
}*/
/**
* This method can be called outside of the build phase to filter the list of
* available semantic actions.
*
* The actual filtering is happening in the next frame and a frame will be
* scheduled if non is pending.
*
* This is used by [Scrollable] to configure system accessibility tools so
* that they know in which direction a particular list can be scrolled.
*
* If this is never called, then the actions are not filtered. If the list of
* actions to filter changes, it must be called again.
*/
fun replaceSemanticsActions(actions: Set<SemanticsAction>) {
androidx.ui.assert {
val element = context
if (element!!.owner!!.debugBuilding) {
throw FlutterError(
"Unexpected call to replaceSemanticsActions() method of " +
"RawGestureDetectorState.\n" +
"The replaceSemanticsActions() method can only be called outside of " +
"the build phase."
)
}
true
}
if (!widget.excludeFromSemantics) {
(context!!.findRenderObject() as RenderSemanticsGestureHandler).validActions =
actions // will call _markNeedsSemanticsUpdate(), if required.
}
}
override fun dispose() {
for (recognizer: GestureRecognizer in recognizers.values) {
recognizer.dispose()
}
this.recognizers.clear()
super.dispose()
}
private fun syncAll(gestures: Map<KClass<*>, GestureRecognizerFactory<GestureRecognizer>>) {
val oldRecognizers: Map<KClass<*>, GestureRecognizer> = recognizers
recognizers = mutableMapOf()
for ((recognizerType, gestureRecognizerFactory) in gestures.entries) {
// TODO(Migration/shepshapard) : Not possible?
// assert(gestures.get(type)._debugAssertTypeMatches(type))
assert(!recognizers.containsKey(recognizerType))
val gestureRecognizer =
oldRecognizers[recognizerType] ?: gestureRecognizerFactory.constructor()
assert(gestureRecognizer::class == recognizerType)
recognizers[recognizerType] = gestureRecognizer
gestureRecognizerFactory.initializer(gestureRecognizer)
}
for ((recognizerType, gestureRecognizerFactory) in oldRecognizers.entries) {
if (!recognizers.containsKey(recognizerType)) {
gestureRecognizerFactory.dispose()
}
}
}
private fun handlePointerDown(event: PointerDownEvent) {
for (recognizer: GestureRecognizer in recognizers.values) {
recognizer.addPointer(event)
}
}
internal fun handleSemanticsTap() {
val recognizer: GestureRecognizer? =
recognizers[TapGestureRecognizer::class]
assert(recognizer != null)
val tapRecognizer = recognizer as TapGestureRecognizer
tapRecognizer.onTapDown?.let { it(TapDownDetails()) }
tapRecognizer.onTapUp?.let { it(TapUpDetails()) }
tapRecognizer.onTap?.let { it() }
}
internal fun handleSemanticsLongPress() {
val recognizer: GestureRecognizer? =
recognizers[LongPressGestureRecognizer::class]
assert(recognizer != null)
val longRecognizer = recognizer as LongPressGestureRecognizer
longRecognizer.onLongPress?.let { it() }
}
internal fun handleSemanticsHorizontalDragUpdate(updateDetails: DragUpdateDetails) {
val dragRecognizer: GestureRecognizer? =
recognizers[HorizontalDragGestureRecognizer::class]
if (dragRecognizer != null) {
val recognizer = dragRecognizer as HorizontalDragGestureRecognizer
recognizer.onDown?.let {
it(DragDownDetails())
}
recognizer.onStart?.let {
it(DragStartDetails())
}
recognizer.onUpdate?.let {
it(updateDetails)
}
recognizer.onEnd?.let {
it(DragEndDetails(primaryVelocity = 0.0f))
}
return
}
val panRecognizer: GestureRecognizer? =
recognizers.get(PanGestureRecognizer::class)
if (panRecognizer != null) {
val recognizer = panRecognizer as PanGestureRecognizer
recognizer.onDown?.let {
it(DragDownDetails())
}
recognizer.onStart?.let {
it(DragStartDetails())
}
recognizer.onUpdate?.let {
it(updateDetails)
}
recognizer.onEnd?.let {
it(DragEndDetails())
}
return
}
}
internal fun handleSemanticsVerticalDragUpdate(updateDetails: DragUpdateDetails) {
val dragRecognizer: GestureRecognizer? =
recognizers.get(VerticalDragGestureRecognizer::class)
if (dragRecognizer != null) {
val recognizer = dragRecognizer as VerticalDragGestureRecognizer
recognizer.onDown?.let {
it(DragDownDetails())
}
recognizer.onStart?.let {
it(DragStartDetails())
}
recognizer.onUpdate?.let {
it(updateDetails)
}
recognizer.onEnd?.let {
it(DragEndDetails(primaryVelocity = 0.0f))
}
return
}
val panRecognizer: GestureRecognizer? =
recognizers.get(PanGestureRecognizer::class)
if (panRecognizer != null) {
val recognizer = panRecognizer as VerticalDragGestureRecognizer
recognizer.onDown?.let {
it(DragDownDetails())
}
recognizer.onStart?.let {
it(DragStartDetails())
}
recognizer.onUpdate?.let {
it(updateDetails)
}
recognizer.onEnd?.let {
it(DragEndDetails())
}
return
}
}
override fun build(context: BuildContext): Widget {
var result: Widget = Listener(
onPointerDown = ::handlePointerDown,
behavior = widget.behavior,
child = widget.child
)
if (!widget.excludeFromSemantics) {
result = GestureSemantics(owner = this, child = result)
}
return result
}
override fun debugFillProperties(properties: DiagnosticPropertiesBuilder) {
super.debugFillProperties(properties)
// TODO(migration/shepshapard): Need some other reasonable symbol for being in this state.
if (recognizers == null) {
properties.add(DiagnosticsNode.message("DISPOSED"))
} else {
val gestures: MutableList<String> =
recognizers.values.map { recognizer ->
recognizer.debugDescription
}.toMutableList()
properties.add(IterableProperty("gestures", gestures, ifEmpty = "<none>"))
properties.add(
IterableProperty(
"recognizers",
recognizers.values,
level = DiagnosticLevel.fine
)
)
}
properties.add(EnumProperty("behavior", widget.behavior, defaultValue = null))
}
}
private class GestureSemantics(
key: Key? = null,
child: Widget? = null,
val owner: RawGestureDetectorState
) : SingleChildRenderObjectWidget(
key = key, child = child
) {
override fun createRenderObject(context: BuildContext): RenderSemanticsGestureHandler {
return RenderSemanticsGestureHandler(
onTap = onTapHandler,
onLongPress = onLongPressHandler,
onHorizontalDragUpdate = onHorizontalDragUpdateHandler,
onVerticalDragUpdate = onVerticalDragUpdateHandler
)
}
override fun updateRenderObject(
context: BuildContext,
renderObject: RenderObject
) {
this.updateHandlers(renderObject as RenderSemanticsGestureHandler)
}
internal fun updateHandlers(renderObject: RenderObject) {
(renderObject as RenderSemanticsGestureHandler).onTap = onTapHandler
}
private val onTapHandler: GestureTapCallback?
get() =
if (owner.recognizers.containsKey(TapGestureRecognizer::class))
owner::handleSemanticsTap
else null
private val onLongPressHandler: GestureTapCallback?
get() =
if (owner.recognizers.containsKey(LongPressGestureRecognizer::class))
owner::handleSemanticsLongPress
else null
private val onHorizontalDragUpdateHandler: GestureDragUpdateCallback?
get() =
if (owner.recognizers.containsKey(HorizontalDragGestureRecognizer::class) ||
owner.recognizers.containsKey(PanGestureRecognizer::class))
owner::handleSemanticsHorizontalDragUpdate
else null
private val onVerticalDragUpdateHandler: GestureDragUpdateCallback?
get() =
if (owner.recognizers.containsKey(VerticalDragGestureRecognizer::class) ||
owner.recognizers.containsKey(PanGestureRecognizer::class))
owner::handleSemanticsVerticalDragUpdate
else null
}