diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/action/KeyAction.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/action/KeyAction.kt index 7b3b82fd..75132f7b 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/action/KeyAction.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/model/action/KeyAction.kt @@ -422,6 +422,8 @@ enum class KeyAction( KeySet(Key.P), false, ), + SetCurrentEntryLeft(listOf(Strings.ActionSetCurrentEntryLeft), null, false), + SetCurrentEntryRight(listOf(Strings.ActionSetCurrentEntryRight), null, false), SetProperty1(listOf(Strings.ActionSetProperty1), null, false), SetProperty2(listOf(Strings.ActionSetProperty2), null, false), SetProperty3(listOf(Strings.ActionSetProperty3), null, false), diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerState.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerState.kt index 6fd596a3..504b0699 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerState.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerState.kt @@ -91,20 +91,31 @@ class MarkerState( return index % (labelerConf.fields.size + 1) } + /** + * The flattened point index of the given entry's start border. Returns [MarkerCursorState.START_POINT_INDEX] for + * the first entry, whose start is the shared canvas start rather than an inner border. + */ + fun getStartBorderPointIndex(entryIndex: Int): Int = if (entryIndex == 0) { + MarkerCursorState.START_POINT_INDEX + } else { + entryIndex * (labelerConf.fields.size + 1) - 1 + } + + /** + * The flattened point index of the given entry's end border. Returns [MarkerCursorState.END_POINT_INDEX] for the + * last entry, whose end is the shared canvas end rather than an inner border. + */ + fun getEndBorderPointIndex(entryIndex: Int): Int = if (entryIndex == entriesInPixel.indices.last()) { + MarkerCursorState.END_POINT_INDEX + } else { + // the end border's flattened index, derived from the entry index directly (not from the start border index, + // which is overridden to START_POINT_INDEX for the first entry and would otherwise mis-compute it) + (entryIndex + 1) * (labelerConf.fields.size + 1) - 1 + } + fun getPointIndexAsSingleEntry(entryIndex: Int, pointIndex: Int): Int { - val entryIndices = entriesInPixel.indices - val startPointIndex = if (entryIndex == 0) { - MarkerCursorState.START_POINT_INDEX - } else { - entryIndex * (labelerConf.fields.size + 1) - 1 - } - val endPointIndex = if (entryIndex == entryIndices.last()) { - MarkerCursorState.END_POINT_INDEX - } else { - // the end border's flattened index, derived from the entry index directly (not from startPointIndex, - // which is overridden to START_POINT_INDEX for the first entry and would otherwise mis-compute it) - (entryIndex + 1) * (labelerConf.fields.size + 1) - 1 - } + val startPointIndex = getStartBorderPointIndex(entryIndex) + val endPointIndex = getEndBorderPointIndex(entryIndex) return when (pointIndex) { startPointIndex -> MarkerCursorState.START_POINT_INDEX endPointIndex -> MarkerCursorState.END_POINT_INDEX @@ -476,6 +487,14 @@ class MarkerState( return null } + /** + * Returns the index (within [entriesInPixel]) of the entry whose actual range contains [position], or null if the + * cursor is not over any entry. + */ + fun getEntryIndexByCursorPosition(position: Float): Int? = entriesInPixel.indexOfFirst { + it.getActualStart(labelerConf) <= position && position <= it.getActualEnd(labelerConf) + }.takeIf { it >= 0 } + fun isValidCutPosition(position: Float) = entriesInPixel.any { it.isValidCutPosition(position) } fun isValidPlaybackPosition(position: Float) = position < entryConverter.convertToPixel(sampleLengthMillis) @@ -498,32 +517,12 @@ class MarkerState( appConf: AppConf, labelerConf: LabelerConf, ): Pair, Int>? { - val paramIndex = when (action) { - KeyAction.SetValue1 -> 0 - KeyAction.SetValue2 -> 1 - KeyAction.SetValue3 -> 2 - KeyAction.SetValue4 -> 3 - KeyAction.SetValue5 -> 4 - KeyAction.SetValue6 -> 5 - KeyAction.SetValue7 -> 6 - KeyAction.SetValue8 -> 7 - KeyAction.SetValue9 -> 8 - KeyAction.SetValue10 -> 9 - else -> return null - } - - // Only used in single edit mode - if (entries.size != 1) return null - - val fieldCount = this.labelerConf.fields.filter { it.shortcutIndex != null }.size - val pointIndex = when { - paramIndex == 0 -> MarkerCursorState.START_POINT_INDEX - paramIndex == fieldCount + 1 -> MarkerCursorState.END_POINT_INDEX - paramIndex <= fieldCount -> this.labelerConf.fields.indexOfFirst { it.shortcutIndex == paramIndex } - .takeIf { it >= 0 } ?: return null - else -> return null - } val cursorPosition = cursorState.value.position ?: return null + val pointIndex = if (entries.size == 1) { + getSingleEditPointIndexByKeyAction(action) ?: return null + } else { + getMultiEditPointIndexByKeyAction(action, cursorPosition) ?: return null + } val lockDrag = appConf.editor.lockedSettingParameterWithCursor && when (appConf.editor.lockedDrag) { AppConf.Editor.LockedDrag.UseLabeler -> { @@ -545,6 +544,59 @@ class MarkerState( return entries to pointIndex } + /** + * Resolves the flattened point index for a `SetValueN` key action in single entry edit mode, where the parameter + * refers to the single edited entry's start/end/field points. + */ + private fun getSingleEditPointIndexByKeyAction(action: KeyAction): Int? { + val paramIndex = when (action) { + KeyAction.SetValue1 -> 0 + KeyAction.SetValue2 -> 1 + KeyAction.SetValue3 -> 2 + KeyAction.SetValue4 -> 3 + KeyAction.SetValue5 -> 4 + KeyAction.SetValue6 -> 5 + KeyAction.SetValue7 -> 6 + KeyAction.SetValue8 -> 7 + KeyAction.SetValue9 -> 8 + KeyAction.SetValue10 -> 9 + else -> return null + } + val fieldCount = labelerConf.fields.filter { it.shortcutIndex != null }.size + return when { + paramIndex == 0 -> MarkerCursorState.START_POINT_INDEX + paramIndex == fieldCount + 1 -> MarkerCursorState.END_POINT_INDEX + paramIndex <= fieldCount -> labelerConf.fields.indexOfFirst { it.shortcutIndex == paramIndex } + .takeIf { it >= 0 } ?: return null + else -> return null + } + } + + /** + * Resolves the flattened point index for a border-setting key action in multiple entry edit mode. Only borders can + * be set here, since fields cannot be identified unambiguously across a group of entries: + * - [KeyAction.SetValue1] / [KeyAction.SetValue2] target the left / right border of the entry under the cursor. + * - [KeyAction.SetCurrentEntryLeft] / [KeyAction.SetCurrentEntryRight] target the left / right border of the + * current entry regardless of where the cursor is. + * + * Returns null when the action is not a supported border action, or when the target entry cannot be located. + */ + private fun getMultiEditPointIndexByKeyAction(action: KeyAction, cursorPosition: Float): Int? { + val entryIndex = when (action) { + KeyAction.SetValue1, KeyAction.SetValue2 -> + getEntryIndexByCursorPosition(cursorPosition) ?: return null + KeyAction.SetCurrentEntryLeft, KeyAction.SetCurrentEntryRight -> + entries.indexOfFirst { it.index == project.currentModule.currentIndex }.takeIf { it >= 0 } + ?: return null + else -> return null + } + return when (action) { + KeyAction.SetValue1, KeyAction.SetCurrentEntryLeft -> getStartBorderPointIndex(entryIndex) + KeyAction.SetValue2, KeyAction.SetCurrentEntryRight -> getEndBorderPointIndex(entryIndex) + else -> null + } + } + val isCursor: Boolean get() = scissorsState.value == null && panState.value == null && playbackState.value == null diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt index 6df94307..fb6e3601 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/Strings.kt @@ -549,6 +549,8 @@ enum class Strings { ActionSetValue8, ActionSetValue9, ActionSetValue10, + ActionSetCurrentEntryLeft, + ActionSetCurrentEntryRight, ActionSetProperty1, ActionSetProperty2, ActionSetProperty3, diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt index dec47402..2b5171a9 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsChineseSimplified.kt @@ -592,8 +592,8 @@ fun Strings.zhHans(): String? = when (this) { ActionInputResolution -> "输入画布分辨率" ActionCancelDialog -> "关闭对话框" ActionScissorsCut -> "在当前光标位置切割" - ActionSetValue1 -> "将参数1设置到光标位置" - ActionSetValue2 -> "将参数2设置到光标位置" + ActionSetValue1 -> "将参数1设置到光标位置(多条目编辑模式下为光标所在条目的左边界)" + ActionSetValue2 -> "将参数2设置到光标位置(多条目编辑模式下为光标所在条目的右边界)" ActionSetValue3 -> "将参数3设置到光标位置" ActionSetValue4 -> "将参数4设置到光标位置" ActionSetValue5 -> "将参数5设置到光标位置" @@ -602,6 +602,8 @@ fun Strings.zhHans(): String? = when (this) { ActionSetValue8 -> "将参数8设置到光标位置" ActionSetValue9 -> "将参数9设置到光标位置" ActionSetValue10 -> "将参数10设置到光标位置" + ActionSetCurrentEntryLeft -> "将当前条目的左边界设置到光标位置(多条目编辑模式)" + ActionSetCurrentEntryRight -> "将当前条目的右边界设置到光标位置(多条目编辑模式)" ActionSetProperty1 -> "输入属性1的值" ActionSetProperty2 -> "输入属性2的值" ActionSetProperty3 -> "输入属性3的值" diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt index 8b8ad269..d8691f90 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsEnglish.kt @@ -670,8 +670,8 @@ fun Strings.en(): String = when (this) { ActionInputResolution -> "Input Canvas Resolution" ActionCancelDialog -> "Close Dialog" ActionScissorsCut -> "Cut at Current Cursor Position" - ActionSetValue1 -> "Set Parameter 1 To Cursor Position" - ActionSetValue2 -> "Set Parameter 2 To Cursor Position" + ActionSetValue1 -> "Set Parameter 1 To Cursor Position (Left Border Of Entry Under Cursor In Multi-Edit Mode)" + ActionSetValue2 -> "Set Parameter 2 To Cursor Position (Right Border Of Entry Under Cursor In Multi-Edit Mode)" ActionSetValue3 -> "Set Parameter 3 To Cursor Position" ActionSetValue4 -> "Set Parameter 4 To Cursor Position" ActionSetValue5 -> "Set Parameter 5 To Cursor Position" @@ -680,6 +680,8 @@ fun Strings.en(): String = when (this) { ActionSetValue8 -> "Set Parameter 8 To Cursor Position" ActionSetValue9 -> "Set Parameter 9 To Cursor Position" ActionSetValue10 -> "Set Parameter 10 To Cursor Position" + ActionSetCurrentEntryLeft -> "Set Current Entry's Left Border To Cursor Position (Multiple Entry Edit Mode)" + ActionSetCurrentEntryRight -> "Set Current Entry's Right Border To Cursor Position (Multiple Entry Edit Mode)" ActionSetProperty1 -> "Input Value for Property 1" ActionSetProperty2 -> "Input Value for Property 2" ActionSetProperty3 -> "Input Value for Property 3" diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt index c1a5ed46..a89bfa69 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsJapanese.kt @@ -628,8 +628,8 @@ fun Strings.ja(): String? = when (this) { ActionInputResolution -> "キャンパス解像度を入力" ActionCancelDialog -> "ダイアログを閉じる" ActionScissorsCut -> "カーソル位置でカット" - ActionSetValue1 -> "パラメータ1をカーソル位置に設定" - ActionSetValue2 -> "パラメータ2をカーソル位置に設定" + ActionSetValue1 -> "パラメータ1をカーソル位置に設定(複数エントリー編集モードではカーソル位置のエントリーの左境界)" + ActionSetValue2 -> "パラメータ2をカーソル位置に設定(複数エントリー編集モードではカーソル位置のエントリーの右境界)" ActionSetValue3 -> "パラメータ3をカーソル位置に設定" ActionSetValue4 -> "パラメータ4をカーソル位置に設定" ActionSetValue5 -> "パラメータ5をカーソル位置に設定" @@ -638,6 +638,8 @@ fun Strings.ja(): String? = when (this) { ActionSetValue8 -> "パラメータ8をカーソル位置に設定" ActionSetValue9 -> "パラメータ9をカーソル位置に設定" ActionSetValue10 -> "パラメータ10をカーソル位置に設定" + ActionSetCurrentEntryLeft -> "現在のエントリーの左境界をカーソル位置に設定(複数エントリー編集モード)" + ActionSetCurrentEntryRight -> "現在のエントリーの右境界をカーソル位置に設定(複数エントリー編集モード)" ActionSetProperty1 -> "プロパティ1の値を入力" ActionSetProperty2 -> "プロパティ2の値を入力" ActionSetProperty3 -> "プロパティ3の値を入力" diff --git a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt index 34acf24a..d49f351e 100644 --- a/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt +++ b/src/jvmMain/kotlin/com/sdercolin/vlabeler/ui/string/StringsKorean.kt @@ -647,8 +647,8 @@ fun Strings.ko(): String? = when (this) { ActionInputResolution -> "캔버스 해상도 입력" ActionCancelDialog -> "대화창 닫기" ActionScissorsCut -> "자르기 (커서 위치에서)" - ActionSetValue1 -> "1번 마커 놓기 (커서 위치에)" - ActionSetValue2 -> "2번 마커 놓기 (커서 위치에)" + ActionSetValue1 -> "1번 마커 놓기 (커서 위치에) (다중 엔트리 편집 모드에서는 커서 위치 엔트리의 왼쪽 경계)" + ActionSetValue2 -> "2번 마커 놓기 (커서 위치에) (다중 엔트리 편집 모드에서는 커서 위치 엔트리의 오른쪽 경계)" ActionSetValue3 -> "3번 마커 놓기 (커서 위치에)" ActionSetValue4 -> "4번 마커 놓기 (커서 위치에)" ActionSetValue5 -> "5번 마커 놓기 (커서 위치에)" @@ -657,6 +657,8 @@ fun Strings.ko(): String? = when (this) { ActionSetValue8 -> "8번 마커 놓기 (커서 위치에)" ActionSetValue9 -> "9번 마커 놓기 (커서 위치에)" ActionSetValue10 -> "10번 마커 놓기 (커서 위치에)" + ActionSetCurrentEntryLeft -> "현재 엔트리의 왼쪽 경계를 커서 위치에 놓기 (다중 엔트리 편집 모드)" + ActionSetCurrentEntryRight -> "현재 엔트리의 오른쪽 경계를 커서 위치에 놓기 (다중 엔트리 편집 모드)" ActionSetProperty1 -> "1번 값 입력" ActionSetProperty2 -> "2번 값 입력" ActionSetProperty3 -> "3번 값 입력" diff --git a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateFactory.kt b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateFactory.kt index b047076c..519a4e63 100644 --- a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateFactory.kt +++ b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateFactory.kt @@ -50,12 +50,14 @@ object MarkerStateFactory { * @param labelerConf the labeler in use. * @param allEntries all entries of the current module (== the current group), in millis. * @param editedIndexes indexes in [allEntries] that are being edited (all of them by default). + * @param currentIndex the module's current entry index (the first edited entry by default). * @param parallelModules additional modules that are parallel to the current module (sharing the raw label file). */ fun create( labelerConf: LabelerConf, allEntries: List, editedIndexes: List = allEntries.indices.toList(), + currentIndex: Int = editedIndexes.first(), sampleRate: Float = 1000f, resolution: Int = 1, sampleLengthMillis: Float = DEFAULT_SAMPLE_LENGTH_MILLIS, @@ -65,7 +67,7 @@ object MarkerStateFactory { name = "main", sampleDirectoryPath = "/samples", entries = allEntries, - currentIndex = editedIndexes.first(), + currentIndex = currentIndex, rawFilePath = RAW_FILE_PATH, ) val project = Project( diff --git a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt index a3cebb73..db6c9832 100644 --- a/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt +++ b/src/jvmTest/kotlin/com/sdercolin/vlabeler/ui/editor/labeler/marker/MarkerStateMiscTest.kt @@ -31,12 +31,13 @@ class MarkerStateMiscTest { ), ) - private fun continuousState() = MarkerStateFactory.create( + private fun continuousState(currentIndex: Int = 0) = MarkerStateFactory.create( labelerConf = TestLabelers.nnsvsSinger, allEntries = listOf( MarkerStateFactory.entry(100f, 200f, name = "a"), MarkerStateFactory.entry(200f, 350f, name = "b"), ), + currentIndex = currentIndex, ) // region tools @@ -167,15 +168,105 @@ class MarkerStateMiscTest { } @Test - fun setValueInMultiEntryModeReturnsNull() { + fun nonSetValueActionReturnsNull() { + val state = utauSingerState() + assertNull(state.setValueWithCursorAt(KeyAction.NewProject, 150f)) + } + + // endregion + + // region getUpdatedEntriesByKeyAction in multiple entry edit mode + + @Test + fun setValue1InMultiEntryModeSetsLeftBorderOfEntryUnderCursor() { val state = continuousState() - assertNull(state.setValueWithCursorAt(KeyAction.SetValue1, 150f)) + // cursor 150 is inside entry "a" (100..200), the first entry, so its left border is the group start + val result = state.setValueWithCursorAt(KeyAction.SetValue1, 150f) + assertNotNull(result) + val (entries, pointIndex) = result + assertEquals(MarkerCursorState.START_POINT_INDEX, pointIndex) + assertEquals(state.entriesInPixel[0].copy(start = 150f), entries[0]) } @Test - fun nonSetValueActionReturnsNull() { + fun setValue1InMultiEntryModeSetsInnerBorderWhenCursorInSecondEntry() { + val state = continuousState() + // cursor 250 is inside entry "b" (200..350); its left border is the inner border between "a" and "b" + val result = state.setValueWithCursorAt(KeyAction.SetValue1, 250f) + assertNotNull(result) + val (entries, pointIndex) = result + assertEquals(0, pointIndex) + assertEquals(state.entriesInPixel[0].copy(end = 250f), entries[0]) + assertEquals(state.entriesInPixel[1].copy(start = 250f), entries[1]) + } + + @Test + fun setValue2InMultiEntryModeSetsRightBorderOfEntryUnderCursor() { + val state = continuousState() + // cursor 150 is inside entry "a"; its right border is the inner border between "a" and "b" + val result = state.setValueWithCursorAt(KeyAction.SetValue2, 150f) + assertNotNull(result) + val (entries, pointIndex) = result + assertEquals(0, pointIndex) + assertEquals(state.entriesInPixel[0].copy(end = 150f), entries[0]) + assertEquals(state.entriesInPixel[1].copy(start = 150f), entries[1]) + } + + @Test + fun setValue2InMultiEntryModeSetsGroupEndWhenCursorInLastEntry() { + val state = continuousState() + // cursor 300 is inside entry "b" (200..350), the last entry, so its right border is the group end + val result = state.setValueWithCursorAt(KeyAction.SetValue2, 300f) + assertNotNull(result) + val (entries, pointIndex) = result + assertEquals(MarkerCursorState.END_POINT_INDEX, pointIndex) + assertEquals(state.entriesInPixel[1].copy(end = 300f), entries[1]) + } + + @Test + fun setValueInMultiEntryModeWithCursorOutsideAllEntriesReturnsNull() { + val state = continuousState() + // 900 is past the last entry's end (350), so no entry is under the cursor + assertNull(state.setValueWithCursorAt(KeyAction.SetValue1, 900f)) + } + + @Test + fun setValue3AndBeyondAreNotSupportedInMultiEntryMode() { + val state = continuousState() + assertNull(state.setValueWithCursorAt(KeyAction.SetValue3, 250f)) + } + + @Test + fun setCurrentEntryLeftUsesCurrentEntryRegardlessOfCursor() { + // current entry is "b" (index 1) while the cursor is inside "a" + val state = continuousState(currentIndex = 1) + val result = state.setValueWithCursorAt(KeyAction.SetCurrentEntryLeft, 150f) + assertNotNull(result) + val (entries, pointIndex) = result + // "b"'s left border is the inner border, not the group start that a cursor-based lookup on "a" would give + assertEquals(0, pointIndex) + assertEquals(state.entriesInPixel[0].copy(end = 150f), entries[0]) + assertEquals(state.entriesInPixel[1].copy(start = 150f), entries[1]) + } + + @Test + fun setCurrentEntryRightUsesCurrentEntryRegardlessOfCursor() { + // current entry is "a" (index 0) while the cursor is inside "b" + val state = continuousState(currentIndex = 0) + val result = state.setValueWithCursorAt(KeyAction.SetCurrentEntryRight, 250f) + assertNotNull(result) + val (entries, pointIndex) = result + // "a"'s right border is the inner border, not the group end that a cursor-based lookup on "b" would give + assertEquals(0, pointIndex) + assertEquals(state.entriesInPixel[0].copy(end = 250f), entries[0]) + assertEquals(state.entriesInPixel[1].copy(start = 250f), entries[1]) + } + + @Test + fun setCurrentEntryActionsAreIgnoredInSingleEntryMode() { val state = utauSingerState() - assertNull(state.setValueWithCursorAt(KeyAction.NewProject, 150f)) + assertNull(state.setValueWithCursorAt(KeyAction.SetCurrentEntryLeft, 200f)) + assertNull(state.setValueWithCursorAt(KeyAction.SetCurrentEntryRight, 200f)) } // endregion