1 /*
<lambda>null2  * Copyright (C) 2022 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.compose.ui.graphics.painter
18 
19 import android.graphics.drawable.Animatable
20 import android.graphics.drawable.ColorDrawable
21 import android.graphics.drawable.Drawable
22 import android.os.Build
23 import android.os.Handler
24 import android.os.Looper
25 import android.view.View
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.RememberObserver
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.remember
31 import androidx.compose.runtime.setValue
32 import androidx.compose.ui.geometry.Size
33 import androidx.compose.ui.graphics.Color
34 import androidx.compose.ui.graphics.ColorFilter
35 import androidx.compose.ui.graphics.asAndroidColorFilter
36 import androidx.compose.ui.graphics.drawscope.DrawScope
37 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
38 import androidx.compose.ui.graphics.nativeCanvas
39 import androidx.compose.ui.graphics.painter.ColorPainter
40 import androidx.compose.ui.graphics.painter.Painter
41 import androidx.compose.ui.graphics.withSave
42 import androidx.compose.ui.unit.LayoutDirection
43 import kotlin.math.roundToInt
44 
45 /**
46  * *************************************************************************************************
47  * This file was forked from
48  * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
49  */
50 private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
51 
52 /**
53  * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
54  * should be remembered to be able to start and stop [Animatable] animations.
55  *
56  * Instances are usually retrieved from [rememberDrawablePainter].
57  */
58 public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver {
59     private var drawInvalidateTick by mutableStateOf(0)
60     private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
61 
<lambda>null62     private val callback: Drawable.Callback by lazy {
63         object : Drawable.Callback {
64             override fun invalidateDrawable(d: Drawable) {
65                 // Update the tick so that we get re-drawn
66                 drawInvalidateTick++
67                 // Update our intrinsic size too
68                 drawableIntrinsicSize = drawable.intrinsicSize
69             }
70 
71             override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
72                 MAIN_HANDLER.postAtTime(what, time)
73             }
74 
75             override fun unscheduleDrawable(d: Drawable, what: Runnable) {
76                 MAIN_HANDLER.removeCallbacks(what)
77             }
78         }
79     }
80 
81     init {
82         if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
83             // Update the drawable's bounds to match the intrinsic size
84             drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
85         }
86     }
87 
onRememberednull88     override fun onRemembered() {
89         drawable.callback = callback
90         drawable.setVisible(true, true)
91         if (drawable is Animatable) drawable.start()
92     }
93 
onAbandonednull94     override fun onAbandoned(): Unit = onForgotten()
95 
96     override fun onForgotten() {
97         if (drawable is Animatable) drawable.stop()
98         drawable.setVisible(false, false)
99         drawable.callback = null
100     }
101 
applyAlphanull102     override fun applyAlpha(alpha: Float): Boolean {
103         drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
104         return true
105     }
106 
applyColorFilternull107     override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
108         drawable.colorFilter = colorFilter?.asAndroidColorFilter()
109         return true
110     }
111 
applyLayoutDirectionnull112     override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
113         if (Build.VERSION.SDK_INT >= 23) {
114             return drawable.setLayoutDirection(
115                 when (layoutDirection) {
116                     LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
117                     LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
118                 }
119             )
120         }
121         return false
122     }
123 
124     override val intrinsicSize: Size
125         get() = drawableIntrinsicSize
126 
onDrawnull127     override fun DrawScope.onDraw() {
128         drawIntoCanvas { canvas ->
129             // Reading this ensures that we invalidate when invalidateDrawable() is called
130             drawInvalidateTick
131 
132             // Update the Drawable's bounds
133             drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
134 
135             canvas.withSave { drawable.draw(canvas.nativeCanvas) }
136         }
137     }
138 }
139 
140 /**
141  * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
142  * contents and use Compose primitives where possible.
143  *
144  * If the provided [drawable] is `null`, an empty no-op painter is returned.
145  *
146  * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
147  * Compose.
148  *
149  * @sample com.google.accompanist.sample.drawablepainter.BasicSample
150  */
151 @Composable
rememberDrawablePainternull152 public fun rememberDrawablePainter(drawable: Drawable?): Painter =
153     remember(drawable) {
154         when (drawable) {
155             null -> EmptyPainter
156             is ColorDrawable -> ColorPainter(Color(drawable.color))
157             // Since the DrawablePainter will be remembered and it implements RememberObserver, it
158             // will receive the necessary events
159             else -> DrawablePainter(drawable.mutate())
160         }
161     }
162 
163 private val Drawable.intrinsicSize: Size
164     get() =
165         when {
166             // Only return a finite size if the drawable has an intrinsic size
167             intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
168                 Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
169             }
170             else -> Size.Unspecified
171         }
172 
173 internal object EmptyPainter : Painter() {
174     override val intrinsicSize: Size
175         get() = Size.Unspecified
onDrawnull176     override fun DrawScope.onDraw() {}
177 }
178