Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4dfd56f8 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Introduce more complex ways to match elements

This CL introduces new ways to combine ElementMatcher's using "and",
"or" and "not" operators.

Bug: 373799480
Test: ElementMatcherTest
Flag: com.android.systemui.scene_container
Change-Id: I7c64260e39dd9ad3682087be19bb1d5db214f7cf
parent 249f87d7
Loading
Loading
Loading
Loading
+40 −7
Original line number Diff line number Diff line
@@ -22,19 +22,52 @@ interface ElementMatcher {
    fun matches(key: ElementKey, content: ContentKey): Boolean
}

/** Returns an [ElementMatcher] that matches any element in [content]. */
fun inContent(content: ContentKey): ElementMatcher {
    val matcherContent = content
    return object : ElementMatcher {
        override fun matches(key: ElementKey, content: ContentKey): Boolean {
            return content == matcherContent
        }
    }
}

/** Returns an [ElementMatcher] that matches all elements not matching [this] matcher. */
operator fun ElementMatcher.not(): ElementMatcher {
    val delegate = this
    return object : ElementMatcher {
        override fun matches(key: ElementKey, content: ContentKey): Boolean {
            return !delegate.matches(key, content)
        }
    }
}

/**
 * Returns an [ElementMatcher] that matches all elements matching both [this] matcher and [other].
 */
infix fun ElementMatcher.and(other: ElementMatcher): ElementMatcher {
    val delegate = this
    return object : ElementMatcher {
        override fun matches(key: ElementKey, content: ContentKey): Boolean {
            return delegate.matches(key, content) && other.matches(key, content)
        }
    }
}

/**
 * Returns an [ElementMatcher] that matches elements in [content] also matching [this]
 * [ElementMatcher].
 * Returns an [ElementMatcher] that matches all elements either [this] matcher, or [other], or both.
 */
fun ElementMatcher.inContent(content: ContentKey): ElementMatcher {
infix fun ElementMatcher.or(other: ElementMatcher): ElementMatcher {
    val delegate = this
    val matcherScene = content
    return object : ElementMatcher {
        override fun matches(key: ElementKey, content: ContentKey): Boolean {
            return content == matcherScene && delegate.matches(key, content)
            return delegate.matches(key, content) || other.matches(key, content)
        }
    }
}

@Deprecated("Use inContent() instead", replaceWith = ReplaceWith("inContent(scene)"))
fun ElementMatcher.inScene(scene: SceneKey) = inContent(scene)
@Deprecated(
    "Use `this and inContent()` instead",
    replaceWith = ReplaceWith("this and inContent(scene)"),
)
fun ElementMatcher.inScene(scene: SceneKey) = this and inContent(scene)
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.compose.animation.scene

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestElements.Bar
import com.android.compose.animation.scene.TestElements.Foo
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ElementMatcherTest {
    @Test
    fun and() {
        val matcher = Foo and inContent(SceneA)
        assertThat(matcher.matches(Foo, SceneA)).isTrue()
        assertThat(matcher.matches(Foo, SceneB)).isFalse()
        assertThat(matcher.matches(Bar, SceneA)).isFalse()
        assertThat(matcher.matches(Bar, SceneB)).isFalse()
    }

    @Test
    fun or() {
        val matcher = Foo or inContent(SceneA)
        assertThat(matcher.matches(Foo, SceneA)).isTrue()
        assertThat(matcher.matches(Foo, SceneB)).isTrue()
        assertThat(matcher.matches(Bar, SceneA)).isTrue()
        assertThat(matcher.matches(Bar, SceneB)).isFalse()
    }

    @Test
    fun not() {
        val matcher = !Foo
        assertThat(matcher.matches(Foo, SceneA)).isFalse()
        assertThat(matcher.matches(Foo, SceneB)).isFalse()
        assertThat(matcher.matches(Bar, SceneA)).isTrue()
        assertThat(matcher.matches(Bar, SceneB)).isTrue()
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -2221,8 +2221,8 @@ class ElementTest {
                            // In A => B, Foo is not shared and first fades out from A then fades in
                            // B.
                            sharedElement(TestElements.Foo, enabled = false)
                            fractionRange(end = 0.5f) { fade(TestElements.Foo.inContent(SceneA)) }
                            fractionRange(start = 0.5f) { fade(TestElements.Foo.inContent(SceneB)) }
                            fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) }
                            fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) }
                        }

                        from(SceneB, to = SceneA) {
+4 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -205,7 +206,8 @@ class OverlayTest {
        val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB))
        val elementChildTag = "elementChildTag"

        fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content)
        fun elementChild(content: ContentKey) =
            hasTestTag(elementChildTag) and SemanticsMatcher.inContent(content)

        @Composable
        fun ContentScope.MovableBar() {
@@ -773,7 +775,7 @@ class OverlayTest {
        // Overscroll on Overlay A.
        scope.launch { state.startTransition(transition(SceneA, OverlayA, progress = { 1.5f })) }
        rule
            .onNode(hasTestTag(movableElementChildTag) and inContent(SceneA))
            .onNode(hasTestTag(movableElementChildTag) and SemanticsMatcher.inContent(SceneA))
            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
            .assertSizeIsEqualTo(100.dp)
            .assertIsDisplayed()
+3 −3
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TestElements
import com.android.compose.animation.scene.TestScenes
import com.android.compose.animation.scene.inContent
import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
import com.android.compose.test.assertSizeIsEqualTo
import org.junit.Rule
@@ -125,10 +125,10 @@ class SharedElementTest {
                sharedElement(TestElements.Foo, enabled = false)

                // In SceneA, Foo leaves to the left edge.
                translate(TestElements.Foo.inContent(TestScenes.SceneA), Edge.Left)
                translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left)

                // In SceneB, Foo comes from the bottom edge.
                translate(TestElements.Foo.inContent(TestScenes.SceneB), Edge.Bottom)
                translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom)
            },
        ) {
            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) }
Loading