Skip to content

Commit

Permalink
Merge pull request #224 from amosproj/gui/feature/disable-nav-buttons
Browse files Browse the repository at this point in the history
feat(gui): disable the navigation buttons if their actions are not possible
  • Loading branch information
akriese authored Jan 20, 2024
2 parents 22a92f0 + d8eb38c commit 0f2f260
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 52 deletions.
57 changes: 47 additions & 10 deletions GUI/src/main/kotlin/frameNavigation/FrameNavigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -237,26 +237,63 @@ class FrameNavigation(state: MutableState<AppState>, val scope: CoroutineScope)
}

/**
* Jump to the next diff.
* Jump to the next diff. If no next diff exists, nothing happens.
* @param forward [Boolean] containing whether to jump forward or backward.
* @return [Unit]
*/
override fun jumpToNextDiff(forward: Boolean) {
// get the current frame
var index = currentIndex
// create a function that increments or decrements the index
val op: (Int) -> Int = if (forward) { x: Int -> x + 1 } else { x: Int -> x - 1 }
// ignore current frame by jumping once
index = op(index)
// jump until a diff is found or the end is reached
while (index >= 0 && index < diffSequence.size && diffSequence[index] == AlignmentElement.PERFECT) {
index = op(index)
val index = nextDiffIndex(forward)

// dont jump to a frame, if no next diff exists
if (index == -1) {
return
}

// jump to the frame
currentIndex = index
jumpToFrame()
}

/**
* Check if there is a next frame in the specified direction.
* @param forward [Boolean] containing whether to check forward or backward.
* @return [Boolean] containing whether there is a next frame.
*/
fun hasNextFrame(forward: Boolean): Boolean {
return if (forward) {
currentDiffIndex.value < diffSequence.size - 1
} else {
currentDiffIndex.value > 0
}
}

/**
* Check if there is a next difference (insertion, deletion, pixel diff) in the specified direction.
* @param forward [Boolean] containing whether to check forward or backward.
* @return [Boolean] containing whether there is a next diff.
*/
fun hasNextDiff(forward: Boolean): Boolean {
return nextDiffIndex(forward) != -1
}

/**
* Get the index of the next diff in the specified direction.
*
* IMPORTANT: This has to use the mutableState variable currentDiffIndex instead of currentIndex,
* as otherwise no UI update will be triggered with a state change.
*
* @param forward [Boolean] containing whether to check forward or backward.
* @return [Int] containing the index of the next diff.
*/
private fun nextDiffIndex(forward: Boolean): Int {
return if (forward) {
val idx = diffSequence.drop(currentDiffIndex.value + 1).indexOfFirst { it != AlignmentElement.PERFECT }
if (idx == -1) -1 else idx + currentDiffIndex.value + 1
} else {
diffSequence.take(currentDiffIndex.value).indexOfLast { it != AlignmentElement.PERFECT }
}
}

/**
* Get count of frames in diff
* @return [Int] containing the number of frames in the diff.
Expand Down
39 changes: 39 additions & 0 deletions GUI/src/main/kotlin/logic/caches/ThumbnailCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package logic.caches

import androidx.compose.ui.graphics.ImageBitmap

/**
* A class that caches thumbnails for the timeline.
*
* @param getImages A function that returns the thumbnails for a given diff index.
*/
class ThumbnailCache(val maxCacheSize: Int, val getImages: (Int) -> List<ImageBitmap>) {
private val cache = mutableMapOf<Int, List<ImageBitmap>>()

// kind of a queue of diff indices ordered by most recently used
private val recentlyUsed = mutableListOf<Int>()

/**
* Returns the thumbnails for a given diff index.
*
* If the thumbnails are not already cached, they are loaded via the `getImages` function and cached.
*
* @param index [Int] containing the index of the diff.
* @return [List]<[ImageBitmap]> containing the thumbnails for the given diff index.
*/
fun get(index: Int): List<ImageBitmap> {
recentlyUsed.remove(index)

if (!cache.containsKey(index)) {
cache[index] = getImages(index)
}

// if the queue is full, remove the least recently used item
if (recentlyUsed.size >= maxCacheSize) {
cache.remove(recentlyUsed.removeAt(0))
}

recentlyUsed.add(index)
return cache[index]!!
}
}
18 changes: 13 additions & 5 deletions GUI/src/main/kotlin/ui/components/diffScreen/NavigationRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package ui.components.diffScreen

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import frameNavigation.FrameNavigation
import ui.components.general.SvgButton
Expand All @@ -22,9 +22,17 @@ fun NavigationButtons(
rowModifier: Modifier = Modifier,
) {
Row(modifier = rowModifier.fillMaxWidth()) {
SvgButton(onClick = { navigator.jumpToNextDiff(false) }, content = "skipStart.svg", modifier = buttonModifier)
SvgButton(onClick = { navigator.jumpFrames(-1) }, content = "skipPrev.svg", modifier = buttonModifier)
SvgButton(onClick = { navigator.jumpFrames(1) }, content = "skipNext.svg", modifier = buttonModifier)
SvgButton(onClick = { navigator.jumpToNextDiff(true) }, content = "skipEnd.svg", modifier = buttonModifier)
SvgButton(onClick = {
navigator.jumpToNextDiff(false)
}, content = "skipStart.svg", modifier = buttonModifier, enabled = navigator.hasNextDiff(false))
SvgButton(onClick = {
navigator.jumpFrames(-1)
}, content = "skipPrev.svg", modifier = buttonModifier, enabled = navigator.hasNextFrame(false))
SvgButton(onClick = {
navigator.jumpFrames(1)
}, content = "skipNext.svg", modifier = buttonModifier, enabled = navigator.hasNextFrame(true))
SvgButton(onClick = {
navigator.jumpToNextDiff(true)
}, content = "skipEnd.svg", modifier = buttonModifier, enabled = navigator.hasNextDiff(true))
}
}
38 changes: 1 addition & 37 deletions GUI/src/main/kotlin/ui/components/diffScreen/Timeline.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.dp
import frameNavigation.FrameNavigation
import kotlinx.coroutines.launch
import logic.caches.ThumbnailCache
import ui.components.general.AutoSizeText

/**
* A class that caches thumbnails for the timeline.
*
* @param getImages A function that returns the thumbnails for a given diff index.
*/
class ThumbnailCache(val maxCacheSize: Int, val getImages: (Int) -> List<ImageBitmap>) {
private val cache = mutableMapOf<Int, List<ImageBitmap>>()

// kind of a queue of diff indices ordered by most recently used
private val recentlyUsed = mutableListOf<Int>()

/**
* Returns the thumbnails for a given diff index.
*
* If the thumbnails are not already cached, they are loaded via the `getImages` function and cached.
*
* @param index [Int] containing the index of the diff.
* @return [List]<[ImageBitmap]> containing the thumbnails for the given diff index.
*/
fun get(index: Int): List<ImageBitmap> {
recentlyUsed.remove(index)

if (!cache.containsKey(index)) {
cache[index] = getImages(index)
}

// if the queue is full, remove the least recently used item
if (recentlyUsed.size >= maxCacheSize) {
cache.remove(recentlyUsed.removeAt(0))
}

recentlyUsed.add(index)
return cache[index]!!
}
}

/**
* A Composable function that creates a box to display the timeline.
* @param navigator [FrameNavigation] containing the navigation logic.
Expand Down
3 changes: 3 additions & 0 deletions GUI/src/main/kotlin/ui/components/general/SvgButton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ import androidx.compose.ui.unit.dp
* A Composable function that creates a button.
* @param onClick [Function] to be called when the button is clicked.
* @param content [String] containing the name of the svg file to be displayed.
* @param enabled [Boolean] deciding if the button can be pressed.
* @param modifier [Modifier] to be applied to the [Button].
* @return [Unit]
*/
@Composable
fun SvgButton(
onClick: () -> Unit,
content: String,
enabled: Boolean = true,
modifier: Modifier = Modifier,
) {
Button(
onClick = onClick,
modifier = modifier.padding(40.dp, 20.dp, 40.dp, 20.dp),
enabled = enabled,
) {
Image(
painter = painterResource(content),
Expand Down

0 comments on commit 0f2f260

Please sign in to comment.