Skip to content

Commit

Permalink
Snap zoom to 100%
Browse files Browse the repository at this point in the history
Re: #170
  • Loading branch information
gujjwal00 committed Mar 1, 2024
1 parent 1a1779e commit c92c8c4
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
54 changes: 54 additions & 0 deletions app/src/androidTest/java/com/gaurav/avnc/ui/vnc/FrameStateTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,58 @@ class FrameStateTest {
assertEquals(-2f, snapshot.frameX)
assertEquals(-3f, snapshot.frameY)
}


/*********************** Snapping ****************************************/
@Test
fun noSnappingInInitialState() {
val state = FrameState()

state.updateZoom(1.02f)
assertEquals(1.02f, state.zoomScale)

state.setZoom(1f, 1f)
state.updateZoom(.98f)
assertEquals(.98f, state.zoomScale)
}

@Test
fun snapUpWhenGoingBelow100Percent() {
val state = FrameState()

state.setZoom(2f, 2f)
assertEquals(2f, state.zoomScale)

state.updateZoom(1.5f / 2f)
assertEquals(1.5f, state.zoomScale)

state.updateZoom(1f / 1.5f)
assertEquals(1f, state.zoomScale)

state.updateZoom(.9f / 1f)
assertEquals(1f, state.zoomScale) // 90% should be snapped-up to 100%

state.updateZoom(.5f) // Decreased too much,
assertTrue(state.zoomScale < 1) // should no longer be snapped
}

@Test
fun snapDownWhenGoingAbove100Percent() {
val state = FrameState()

state.setZoom(.5f, .5f)
assertEquals(.5f, state.zoomScale)

state.updateZoom(.8f / .5f)
assertEquals(.8f, state.zoomScale)

state.updateZoom(1f / .8f)
assertEquals(1f, state.zoomScale)

state.updateZoom(1.1f / 1f)
assertEquals(1f, state.zoomScale) // 110% should be snapped-down to 100%

state.updateZoom(2f) // Increased too much,
assertTrue(state.zoomScale > 1) // should no longer be snapped
}
}
5 changes: 4 additions & 1 deletion app/src/main/java/com/gaurav/avnc/ui/vnc/Dispatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ class Dispatcher(private val activity: VncActivity) {
**************************************************************************/

fun onGestureStart() = config.defaultMode.onGestureStart()
fun onGestureStop(p: PointF) = config.defaultMode.onGestureStop(p)
fun onGestureStop(p: PointF) {
config.defaultMode.onGestureStop(p)
viewModel.frameState.onGestureStop()
}

fun onTap1(p: PointF) = config.tap1Action(p)
fun onTap2(p: PointF) = config.tap2Action(p)
Expand Down
57 changes: 56 additions & 1 deletion app/src/main/java/com/gaurav/avnc/ui/vnc/FrameState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package com.gaurav.avnc.ui.vnc
import android.graphics.PointF
import android.graphics.RectF
import com.gaurav.avnc.ui.vnc.FrameState.Snapshot
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

Expand Down Expand Up @@ -188,8 +189,9 @@ class FrameState(
*/
fun updateZoom(scaleFactor: Float): Float = withLock {
val oldScale = zoomScale
val newScale = zoomScale * scaleFactor

zoomScale *= scaleFactor
zoomScale = snapZoom(oldScale, newScale)
coerceValues()

return zoomScale / oldScale //Applied scale factor
Expand Down Expand Up @@ -254,6 +256,13 @@ class FrameState(
vpWidth = vpWidth, vpHeight = vpHeight, scale = scale)
}

/**
* Signaled when gesture ends
*/
fun onGestureStop() {
resetSnapState()
}

private fun calculateBaseScale() {
if (fbHeight == 0F || fbWidth == 0F || windowHeight == 0F)
return //Not enough info yet
Expand Down Expand Up @@ -288,4 +297,50 @@ class FrameState(
return if (diff >= 0) diff / 2 + safeMin //Frame will be smaller than safe area, so center it
else current.coerceIn(diff + safeMin, safeMin) //otherwise, make sure safe area is completely filled.
}


/**
* Zoom scale snapping: It temporarily stops the scale at 100% when user is zooming.
* This way user don't have to reset zoom via "Reset zoom" button in toolbar.
*/
private enum class Snap { None, Up, Down }

private var snapMode = Snap.None
private val snapLimit = 0.3f // ±30% zoom
private var snapDelta = 0f

private fun snapZoom(oldZoom: Float, newZoom: Float): Float {
val deltaToOne = abs(newZoom - 1)
var result = newZoom

if (snapMode == Snap.Up) {
if (newZoom < 1) { // Until snap limit is reached, snap up zoom to 1.0
snapDelta += deltaToOne
if (snapDelta < snapLimit)
result = 1f
} else // Not crossed below 1.0 yet
snapDelta = 0f
}

if (snapMode == Snap.Down) {
if (newZoom > 1) { // Until snap limit is reached, snap down zoom to 1.0
snapDelta += deltaToOne
if (snapDelta < snapLimit)
result = 1f
} else // Not crossed above 1.0 yet
snapDelta = 0f
}

if (oldZoom > 1 && newZoom < oldZoom) // Greater than 1 but decreasing, so snap when going below 1
snapMode = Snap.Up
if (oldZoom < 1 && newZoom > oldZoom) // Lower than 1 but increasing, so snap when going above 1
snapMode = Snap.Down

return result
}

private fun resetSnapState() {
snapMode = Snap.None
snapDelta = 0f
}
}

0 comments on commit c92c8c4

Please sign in to comment.