Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ class PseudoSelectorManager(
private val detachActions = HashMap<String, Runnable>()

private val activeCallbacks = LinkedHashMap<View, PseudoSelectorCallback>()
private val deepestCallbacks = LinkedHashMap<View, PseudoSelectorCallback>()

private val activeDeepestViews = LinkedHashSet<View>()
private val touchListenerViews = HashSet<View>()

fun attach(
tag: Int,
Expand Down Expand Up @@ -104,21 +105,11 @@ class PseudoSelectorManager(
callback: PseudoSelectorCallback,
) {
activeCallbacks[view] = callback
val listener =
View.OnTouchListener { _, event ->
val action = event.actionMasked
if (action == MotionEvent.ACTION_DOWN) {
fireActiveCallbacksUpTree(view, true)
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
fireActiveCallbacksUpTree(view, false)
}
false
}
view.setOnTouchListener(listener)
ensureTouchListener(view)
detachActions[key] =
Runnable {
activeCallbacks.remove(view)
view.setOnTouchListener(null)
maybeRemoveTouchListener(view)
}
}

Expand All @@ -127,27 +118,43 @@ class PseudoSelectorManager(
key: String,
callback: PseudoSelectorCallback,
) {
activeDeepestViews.add(view)
val listener =
View.OnTouchListener { _, event ->
val action = event.actionMasked
if (action == MotionEvent.ACTION_DOWN) {
if (!hasDeepestDescendantAt(view, event.rawX, event.rawY)) {
callback.onSelectorStateChanged(true)
}
deepestCallbacks[view] = callback
ensureTouchListener(view)
detachActions[key] =
Runnable {
deepestCallbacks.remove(view)
maybeRemoveTouchListener(view)
}
}

private fun ensureTouchListener(view: View) {
if (!touchListenerViews.add(view)) {
return
}
view.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
fireActiveCallbacksUpTree(view, true)
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
callback.onSelectorStateChanged(false)
deepestCallbacks[view]?.let {
if (!hasDeepestDescendantAt(view, event.rawX, event.rawY)) {
it.onSelectorStateChanged(true)
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
fireActiveCallbacksUpTree(view, false)
deepestCallbacks[view]?.onSelectorStateChanged(false)
}
false
}
view.setOnTouchListener(listener)
detachActions[key] =
Runnable {
activeDeepestViews.remove(view)
view.setOnTouchListener(null)
}
false
}
}

private fun maybeRemoveTouchListener(view: View) {
if (view !in activeCallbacks && view !in deepestCallbacks) {
touchListenerViews.remove(view)
view.setOnTouchListener(null)
}
}

fun detach(
Expand All @@ -158,18 +165,22 @@ class PseudoSelectorManager(
}

/**
* Returns true if any view in `activeDeepestViews` is a strict descendant of `ancestor`
* and contains the screen point (`rawX`, `rawY`).
* Used by _:active-deepest_ to yield priority to deeper registered views.
* Returns true if any view registered for _:active-deepest_ is a strict descendant of
* `ancestor` and contains the screen point (`rawX`, `rawY`), so the ancestor yields priority
* to the deeper view.
*/
private fun hasDeepestDescendantAt(
ancestor: View,
rawX: Float,
rawY: Float,
): Boolean {
// With fewer than two registered views the only candidate is the ancestor itself.
if (deepestCallbacks.size < 2) {
return false
}
val loc = IntArray(2)
// TODO: Optimize so we don't iterate over all the views with :active-deepest every time.
for (candidate in activeDeepestViews) {
for (candidate in deepestCallbacks.keys) {
if (candidate === ancestor) continue
if (!isDescendantOf(candidate, ancestor)) continue
candidate.getLocationOnScreen(loc)
Expand Down
Loading