@@ -2,7 +2,6 @@ package com.bumptech.glide.integration.compose
2
2
3
3
import android.graphics.PointF
4
4
import android.graphics.drawable.Drawable
5
- import android.util.Log
6
5
import androidx.compose.ui.Alignment
7
6
import androidx.compose.ui.ExperimentalComposeUiApi
8
7
import androidx.compose.ui.Modifier
@@ -12,6 +11,7 @@ import androidx.compose.ui.graphics.ColorFilter
12
11
import androidx.compose.ui.graphics.DefaultAlpha
13
12
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
14
13
import androidx.compose.ui.graphics.drawscope.DrawScope
14
+ import androidx.compose.ui.graphics.drawscope.clipRect
15
15
import androidx.compose.ui.graphics.drawscope.translate
16
16
import androidx.compose.ui.graphics.painter.Painter
17
17
import androidx.compose.ui.graphics.withSave
@@ -36,6 +36,8 @@ import androidx.compose.ui.semantics.semantics
36
36
import androidx.compose.ui.unit.Constraints
37
37
import androidx.compose.ui.unit.IntOffset
38
38
import androidx.compose.ui.unit.IntSize
39
+ import androidx.compose.ui.unit.constrainHeight
40
+ import androidx.compose.ui.unit.constrainWidth
39
41
import androidx.compose.ui.unit.toSize
40
42
import com.bumptech.glide.RequestBuilder
41
43
import com.bumptech.glide.integration.ktx.AsyncGlideSize
@@ -72,7 +74,7 @@ internal fun Modifier.glideNode(
72
74
colorFilter : ColorFilter ? = null,
73
75
transitionFactory : Transition .Factory ? = null,
74
76
requestListener : RequestListener ? = null,
75
- draw : Boolean? = true ,
77
+ draw : Boolean? = null ,
76
78
): Modifier {
77
79
return this then GlideNodeElement (
78
80
requestBuilder,
@@ -151,6 +153,7 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
151
153
private lateinit var requestBuilder: RequestBuilder <Drawable >
152
154
private lateinit var contentScale: ContentScale
153
155
private lateinit var alignment: Alignment
156
+ private lateinit var resolvableGlideSize: ResolvableGlideSize
154
157
private var alpha: Float = DefaultAlpha
155
158
private var colorFilter: ColorFilter ? = null
156
159
private var transitionFactory: Transition .Factory = DoNotTransition .Factory
@@ -163,14 +166,16 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
163
166
// Only used for debugging
164
167
private var drawable: Drawable ? = null
165
168
private var state: RequestState = RequestState .Loading
166
- private var resolvableGlideSize: ResolvableGlideSize ? = null
167
169
private var placeholder: Painter ? = null
168
170
private var isFirstResource = true
169
171
170
172
// Avoid allocating Point/PointFs during draw
171
173
private var placeholderPositionAndSize: CachedPositionAndSize ? = null
172
174
private var drawablePositionAndSize: CachedPositionAndSize ? = null
173
175
176
+ private var hasFixedSize: Boolean = false
177
+ private var inferredGlideSize: com.bumptech.glide.integration.ktx.Size ? = null
178
+
174
179
private var transition: Transition = DoNotTransition
175
180
176
181
private fun RequestBuilder <* >.maybeImmediateSize () =
@@ -199,37 +204,35 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
199
204
this .requestListener = requestListener
200
205
this .draw = draw ? : true
201
206
this .transitionFactory = transitionFactory ? : DoNotTransition .Factory
207
+ this .resolvableGlideSize =
208
+ requestBuilder.maybeImmediateSize()
209
+ ? : inferredGlideSize?.let { ImmediateGlideSize (it) }
210
+ ? : AsyncGlideSize ()
202
211
203
212
if (restartLoad) {
204
213
clear()
205
214
updateDrawable(null )
206
215
207
216
// If we're not attached, we'll be measured when we eventually are attached.
208
217
if (isAttached) {
209
- // If we don't have a fixed size, we need a new layout pass to figure out how large the
210
- // image should be. Ideally we'd retain the old resolved glide size unless some other
211
- // modifier node had already requested measurement. Since we can't tell if measurement is
212
- // requested, we can either re-use the old resolvableGlideSize, which will be incorrect if
213
- // measurement was requested. Or we can invalidate resolvableGlideSize and ensure that it's
214
- // resolved by requesting measurement ourselves. Requesting is less efficient, but more
215
- // correct.
216
- // TODO(sam): See if we can find a reasonable way to remove this behavior, or be more
217
- // targeted.
218
- if (requestBuilder.overrideSize() == null ) {
219
- invalidateMeasurement()
220
- }
221
218
launchRequest(requestBuilder)
222
219
}
223
220
} else {
224
221
invalidateDraw()
225
222
}
226
223
}
227
224
225
+ private val Size .isValidWidth
226
+ get() = this != Size .Unspecified && this .width.isValidDimension
227
+
228
+ private val Size .isValidHeight
229
+ get() = this != Size .Unspecified && this .height.isValidDimension
230
+
228
231
private val Float .isValidDimension
229
- get() = this > 0f
232
+ get() = this > 0f && isFinite()
230
233
231
234
private val Size .isValid
232
- get() = width.isValidDimension && height.isValidDimension
235
+ get() = isValidWidth && isValidHeight
233
236
234
237
private fun Size.roundToInt () = IntSize (width.roundToInt(), height.roundToInt())
235
238
@@ -248,12 +251,12 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
248
251
val currentPositionAndSize = if (cache != null ) {
249
252
cache
250
253
} else {
251
- val srcWidth = if (painter.intrinsicSize.width.isValidDimension ) {
254
+ val srcWidth = if (painter.intrinsicSize.isValidWidth ) {
252
255
painter.intrinsicSize.width
253
256
} else {
254
257
size.width
255
258
}
256
- val srcHeight = if (painter.intrinsicSize.height.isValidDimension ) {
259
+ val srcHeight = if (painter.intrinsicSize.isValidHeight ) {
257
260
painter.intrinsicSize.height
258
261
} else {
259
262
size.height
@@ -275,8 +278,10 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
275
278
)
276
279
}
277
280
278
- translate(currentPositionAndSize.position.x, currentPositionAndSize.position.y) {
279
- drawOne.invoke(this , currentPositionAndSize.size)
281
+ clipRect {
282
+ translate(currentPositionAndSize.position.x, currentPositionAndSize.position.y) {
283
+ drawOne.invoke(this , currentPositionAndSize.size)
284
+ }
280
285
}
281
286
return currentPositionAndSize
282
287
}
@@ -355,11 +360,10 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
355
360
}
356
361
Preconditions .checkArgument(currentJob == null )
357
362
currentJob = (coroutineScope + Dispatchers .Main .immediate).launch {
358
- this @GlideNode.resolvableGlideSize = requestBuilder.maybeImmediateSize() ? : AsyncGlideSize ()
359
363
placeholder = null
360
364
placeholderPositionAndSize = null
361
365
362
- requestBuilder.flowResolvable(resolvableGlideSize!! ).collect {
366
+ requestBuilder.flowResolvable(resolvableGlideSize).collect {
363
367
val (state, drawable) =
364
368
when (it) {
365
369
is Resource -> {
@@ -382,7 +386,11 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
382
386
updateDrawable(drawable)
383
387
requestListener?.onStateChanged(requestBuilder.internalModel, drawable, state)
384
388
this @GlideNode.state = state
385
- invalidateDraw()
389
+ if (hasFixedSize) {
390
+ invalidateDraw()
391
+ } else {
392
+ invalidateMeasurement()
393
+ }
386
394
}
387
395
}
388
396
}
@@ -405,7 +413,6 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
405
413
406
414
private fun clear () {
407
415
isFirstResource = true
408
- resolvableGlideSize = null
409
416
currentJob?.cancel()
410
417
currentJob = null
411
418
state = RequestState .Loading
@@ -419,23 +426,66 @@ internal class GlideNode : DrawModifierNode, LayoutModifierNode, SemanticsModifi
419
426
): MeasureResult {
420
427
placeholderPositionAndSize = null
421
428
drawablePositionAndSize = null
429
+ hasFixedSize = constraints.hasFixedSize()
430
+ inferredGlideSize = constraints.inferredGlideSize()
431
+
422
432
when (val currentSize = resolvableGlideSize) {
423
433
is AsyncGlideSize -> {
424
- val inferredSize = constraints.inferredGlideSize()
425
- if (inferredSize != null ) {
426
- currentSize.setSize(inferredSize)
427
- }
434
+ inferredGlideSize?.also { currentSize.setSize(it) }
428
435
}
429
- // Do nothing.
436
+
430
437
is ImmediateGlideSize -> {}
431
- null -> {}
432
438
}
433
- val placeable = measurable.measure(constraints)
434
- return layout(constraints.maxWidth, constraints.maxHeight ) {
439
+ val placeable = measurable.measure(modifyConstraints( constraints) )
440
+ return layout(placeable.width, placeable.height ) {
435
441
placeable.placeRelative(0 , 0 )
436
442
}
437
443
}
438
444
445
+ private fun Constraints.hasFixedSize () = hasFixedWidth && hasFixedHeight
446
+
447
+ private fun modifyConstraints (constraints : Constraints ): Constraints {
448
+ if (constraints.hasFixedSize()) {
449
+ return constraints.copy(
450
+ minWidth = constraints.maxWidth,
451
+ minHeight = constraints.maxHeight
452
+ )
453
+ }
454
+
455
+ val intrinsicSize = painter?.intrinsicSize ? : return constraints
456
+
457
+ val intrinsicWidth =
458
+ if (constraints.hasFixedWidth) {
459
+ constraints.maxWidth
460
+ } else if (intrinsicSize.isValidWidth) {
461
+ intrinsicSize.width.roundToInt()
462
+ } else {
463
+ constraints.minWidth
464
+ }
465
+
466
+ val intrinsicHeight =
467
+ if (constraints.hasFixedHeight) {
468
+ constraints.maxHeight
469
+ } else if (intrinsicSize.isValidHeight) {
470
+ intrinsicSize.height.roundToInt()
471
+ } else {
472
+ constraints.minHeight
473
+ }
474
+
475
+ val constrainedWidth = constraints.constrainWidth(intrinsicWidth)
476
+ val constrainedHeight = constraints.constrainHeight(intrinsicHeight)
477
+
478
+ val srcSize = Size (intrinsicWidth.toFloat(), intrinsicHeight.toFloat())
479
+ val scaledSize =
480
+ srcSize * contentScale.computeScaleFactor(
481
+ srcSize, Size (constrainedWidth.toFloat(), constrainedHeight.toFloat())
482
+ )
483
+
484
+ val minWidth = constraints.constrainWidth(scaledSize.width.roundToInt())
485
+ val minHeight = constraints.constrainHeight(scaledSize.height.roundToInt())
486
+ return constraints.copy(minWidth = minWidth, minHeight = minHeight)
487
+ }
488
+
439
489
override fun SemanticsPropertyReceiver.applySemantics () {
440
490
displayedDrawable = { drawable }
441
491
}
0 commit comments