3 changed files with 91 additions and 26 deletions
@ -0,0 +1,30 @@
|
||||
package com.utun.chat.ui.screens |
||||
|
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.setValue |
||||
import androidx.compose.ui.geometry.Offset |
||||
import androidx.compose.ui.geometry.Size |
||||
|
||||
/** Общая геометрия увеличения для файла и live: точка под пальцами сохраняется, края ограничивают сдвиг. */ |
||||
internal class VideoZoomState { |
||||
var scale by mutableStateOf(1f) |
||||
private set |
||||
var offset by mutableStateOf(Offset.Zero) |
||||
private set |
||||
|
||||
fun transform(centroid: Offset, pan: Offset, factor: Float, video: Size, viewport: Size) { |
||||
val next = (scale * factor).coerceIn(1f, 4f) |
||||
val ratio = next / scale |
||||
val relative = centroid - Offset(viewport.width / 2f, viewport.height / 2f) |
||||
offset = offset * ratio + relative * (1f - ratio) + pan |
||||
scale = next |
||||
constrain(video, viewport) |
||||
} |
||||
|
||||
fun constrain(video: Size, viewport: Size) { |
||||
val x = ((video.width * scale - viewport.width) / 2f).coerceAtLeast(0f) |
||||
val y = ((video.height * scale - viewport.height) / 2f).coerceAtLeast(0f) |
||||
offset = Offset(offset.x.coerceIn(-x, x), offset.y.coerceIn(-y, y)) |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
|
||||
package com.utun.chat.ui.screens |
||||
|
||||
import androidx.compose.ui.geometry.Offset |
||||
import androidx.compose.ui.geometry.Size |
||||
import org.junit.Assert.assertEquals |
||||
import org.junit.Test |
||||
|
||||
class VideoZoomStateTest { |
||||
private val video = Size(1920f, 1080f) |
||||
private val viewport = Size(2400f, 1080f) |
||||
private val center = Offset(1200f, 540f) |
||||
|
||||
@Test fun pinchKeepsPointUnderFingers() { |
||||
val zoom = VideoZoomState() |
||||
zoom.transform(center + Offset(100f, 50f), Offset.Zero, 2f, video, viewport) |
||||
assertEquals(2f, zoom.scale) |
||||
assertEquals(Offset(-100f, -50f), zoom.offset) |
||||
} |
||||
|
||||
@Test fun panCannotExposeSpaceBeyondVideoEdges() { |
||||
val zoom = VideoZoomState() |
||||
zoom.transform(center, Offset(10000f, -10000f), 2f, video, viewport) |
||||
assertEquals(Offset(720f, -540f), zoom.offset) |
||||
} |
||||
|
||||
@Test fun returningToOneCentersVideoAndPreventsDragging() { |
||||
val zoom = VideoZoomState() |
||||
zoom.transform(center, Offset(300f, 300f), 3f, video, viewport) |
||||
zoom.transform(center, Offset(100f, 100f), 0.1f, video, viewport) |
||||
assertEquals(1f, zoom.scale) |
||||
assertEquals(Offset.Zero, zoom.offset) |
||||
} |
||||
|
||||
@Test fun zoomLimitUsesActualScaleForAnchoring() { |
||||
val zoom = VideoZoomState() |
||||
zoom.transform(center, Offset.Zero, 3f, video, viewport) |
||||
zoom.transform(center + Offset(300f, 0f), Offset.Zero, 2f, video, viewport) |
||||
assertEquals(4f, zoom.scale) |
||||
assertEquals(-100f, zoom.offset.x, 0.001f) |
||||
} |
||||
|
||||
@Test fun changingVideoOrWindowSizeClampsExistingOffset() { |
||||
val zoom = VideoZoomState() |
||||
zoom.transform(center, Offset(700f, 500f), 2f, video, viewport) |
||||
zoom.constrain(Size(600f, 1080f), viewport) |
||||
assertEquals(Offset(0f, 500f), zoom.offset) |
||||
zoom.constrain(Size(1080f, 607.5f), Size(1080f, 2400f)) |
||||
assertEquals(Offset.Zero, zoom.offset) |
||||
assertEquals(2f, zoom.scale) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue