1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.scene.data.model 18 19 import com.android.compose.animation.scene.SceneKey 20 21 /** An immutable stack of [SceneKey]s backed by a singly-linked list. */ 22 sealed interface SceneStack 23 24 /** Returns the scene at the head of the stack, or `null` if empty. O(1) */ SceneStacknull25 fun SceneStack.peek(): SceneKey? = 26 when (this) { 27 EmptyStack -> null 28 is StackedNodes -> head 29 } 30 31 /** Returns a stack with the head removed, or `null` if empty. O(1) */ SceneStacknull32 fun SceneStack.pop(): SceneStack? = 33 when (this) { 34 EmptyStack -> null 35 is StackedNodes -> tail 36 } 37 38 /** Returns a stack with [sceneKey] as the head on top of [this]. O(1) */ SceneStacknull39 fun SceneStack.push(sceneKey: SceneKey): SceneStack = StackedNodes(sceneKey, this) 40 41 /** Returns an iterable that produces all elements in the stack, from head to tail. */ 42 fun SceneStack.asIterable(): Iterable<SceneKey> = Iterable { 43 iterator { 44 when (this@asIterable) { 45 EmptyStack -> {} 46 is StackedNodes -> { 47 yield(head) 48 yieldAll(tail.asIterable()) 49 } 50 } 51 } 52 } 53 54 /** Does this stack contain the given [sceneKey]? O(N) */ SceneStacknull55 fun SceneStack.contains(sceneKey: SceneKey): Boolean = asIterable().any { it == sceneKey } 56 57 /** 58 * Returns a new [SceneStack] containing the given [scenes], ordered such that the first argument is 59 * the head returned from [peek], then the second, and so forth. 60 */ sceneStackOfnull61 fun sceneStackOf(vararg scenes: SceneKey): SceneStack { 62 var result: SceneStack = EmptyStack 63 for (sceneKey in scenes.reversed()) { 64 result = result.push(sceneKey) 65 } 66 return result 67 } 68 69 private data object EmptyStack : SceneStack { toStringnull70 override fun toString() = sceneStackToString() 71 } 72 73 private data class StackedNodes(val head: SceneKey, val tail: SceneStack) : SceneStack { 74 override fun toString() = sceneStackToString() 75 } 76 SceneStacknull77 private fun SceneStack.sceneStackToString(): String = 78 asIterable().joinToString { it.testTag }.let { "SceneStack([$it])" } 79